<commit_before>var _ = require('lodash');
var expect = require('expect.js');
var Model = require('racer/lib/Model');
var Validator = require('./../lib');

beforeEach(function setupModel() {
  this.model = (new Model).at('_page');

  this.model.set('collection', {
    '1': {
      'a': 'a',
      'b': 2,
      'c': {
        'd': 'd'
      },
      'id': '1'
    },
    '2': {
      'a': 'a',
      'b': 2,
      'c': {
        'd': {
          'f': 'f'
        }
      },
      'id': '2'
    }
  });
});

describe('Setup', function () {
  describe('arguments', function () {
    it('throws on no arguments', function () {
      expect(function () {
        new Validator();
      }).to.throwError();
    });

    it('throws on one argument', function () {
      expect(function () {
        var $validator = this.model.at('validator');

        new Validator($validator);
      }.bind(this)).to.throwError();
    });

    it('passing in origin is treated as origin', function () {
      var $validator = this.model.at('validator');
      var $item = this.model.scope('items.1');

      var validator = new Validator($validator, $item);

      expect(validator.fields).to.be(undefined);
      expect(validator.origin).to.not.be(undefined);
    });

    it('fields object with path field is not treated as origin', function () {
      var $validator = this.model.at('validator');

      var validator = new Validator($validator, {path: {}});

      expect(validator.origin).to.be(undefined);
      expect(validator.fields).to.not.be(undefined);
    });

    it('default object (with fields) parameter is not treated as origin', function () {
      var $validator = this.model.at('validator');
      var defaultObject = {path: 'default.path'};
      var fields = {
        path: {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };

      var validator = new Validator($validator, defaultObject, fields);

      expect(validator.origin).to.be(undefined);
      expect(validator.fields).to.not.be(undefined);
    });
  });

  describe('default data', function () {
    it('is automatically set when passed in through fields', function () {
      var $validator = this.model.at('validator');
      var fields = {
        path: {
          'default': 'abc',
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = 'abc';

      new Validator($validator, fields);
      var actual = $validator.get('path.value');

      expect(actual).to.eql(expected);
    });

    it('is automatically set when passed in as defaultObject', function () {
      var $validator = this.model.at('validator');
      var defaultObject = {path: 'default.path'};
      var fields = {
        path: {
          'default': 'abc',
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = 'default.path';

      new Validator($validator, defaultObject, fields);
      var actual = $validator.get('path.value');

      expect(actual).to.eql(expected);
    });
  });
});

describe('Validation', function () {
  describe('Fields', function () {
    it('Required empty field does not validate', function (done) {
      var $validator = this.model.at('validator');
      var fields = {
        path: {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = false;

      var validator = new Validator($validator, fields);

      validator.validateAll(function (actual) {
        expect(actual).to.eql(expected);
        done();
      });
    });

    it('Required field with content does validate', function (done) {
      var $validator = this.model.at('validator');
      var fields = {
        path: {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = true;

      var validator = new Validator($validator, fields);
      $validator.set('path.value', 'something');

      validator.validateAll(function (actual) {
        expect(actual).to.eql(expected);
        done();
      });
    });
  });

  describe('Origin', function () {
    it('Origin copies it\'s data on setup', function () {
      var $validator = this.model.at('validator');
      var $origin = this.model.at('collection.1');
      var fields = {
        'c.d': {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = 'a';

      new Validator($validator, $origin, fields);
      var actual = $validator.get('a.value');

      expect(actual).to.eql(expected);
    });
  });

  describe('Paths', function () {
    describe('Two levels', function () {
      it('Origin copies it\'s data on setup', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = 'd';

        new Validator($validator, $origin, fields);
        var actual = $validator.get('c.d.value');

        expect(actual).to.eql(expected);
      });

      it('Does not validate when required but non-existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does not validate when required but set empty', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        $validator.del('c.d.value');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required and set', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does get all values', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.defaultsDeep({c: {e: 'abc'}}, $origin.get());

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        var actual = validator.getValues();

        expect(actual).to.eql(expected);
      });

      it('Does commit values when valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: 'abc'}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit values when not valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep($origin.get());

        var validator = new Validator($validator, $origin, fields);
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit id', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: 'abc'}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        $validator.set('id.value', '2');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });
    });

    describe('Three levels', function () {
      it('Origin copies it\'s data on setup', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = 'f';

        new Validator($validator, $origin, fields);
        var actual = $validator.get('c.d.f.value');

        expect(actual).to.eql(expected);
      });

      it('Does not validate when required but non-existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does not validate when required but set empty', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        $validator.del('c.d.f.value');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required and set', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does get all values', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.defaultsDeep({c: {e: {f: 'abc'}}}, $origin.get());

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        var actual = validator.getValues();

        expect(actual).to.eql(expected);
      });

      it('Does commit values when valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: {f: 'abc'}}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit values when not valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep($origin.get());

        var validator = new Validator($validator, $origin, fields);
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit id', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: {f: 'abc'}}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        $validator.set('id.value', '3');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });
    });
  });
});
<commit_msg>Add test for changed state in subpaths<commit_after>var _ = require('lodash');
var expect = require('expect.js');
var Model = require('racer/lib/Model');
var Validator = require('./../lib');

beforeEach(function setupModel() {
  this.model = (new Model).at('_page');

  this.model.set('collection', {
    '1': {
      'a': 'a',
      'b': 2,
      'c': {
        'd': 'd'
      },
      'id': '1'
    },
    '2': {
      'a': 'a',
      'b': 2,
      'c': {
        'd': {
          'f': 'f'
        }
      },
      'id': '2'
    }
  });
});

describe('Setup', function () {
  describe('arguments', function () {
    it('throws on no arguments', function () {
      expect(function () {
        new Validator();
      }).to.throwError();
    });

    it('throws on one argument', function () {
      expect(function () {
        var $validator = this.model.at('validator');

        new Validator($validator);
      }.bind(this)).to.throwError();
    });

    it('passing in origin is treated as origin', function () {
      var $validator = this.model.at('validator');
      var $item = this.model.scope('items.1');

      var validator = new Validator($validator, $item);

      expect(validator.fields).to.be(undefined);
      expect(validator.origin).to.not.be(undefined);
    });

    it('fields object with path field is not treated as origin', function () {
      var $validator = this.model.at('validator');

      var validator = new Validator($validator, {path: {}});

      expect(validator.origin).to.be(undefined);
      expect(validator.fields).to.not.be(undefined);
    });

    it('default object (with fields) parameter is not treated as origin', function () {
      var $validator = this.model.at('validator');
      var defaultObject = {path: 'default.path'};
      var fields = {
        path: {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };

      var validator = new Validator($validator, defaultObject, fields);

      expect(validator.origin).to.be(undefined);
      expect(validator.fields).to.not.be(undefined);
    });
  });

  describe('default data', function () {
    it('is automatically set when passed in through fields', function () {
      var $validator = this.model.at('validator');
      var fields = {
        path: {
          'default': 'abc',
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = 'abc';

      new Validator($validator, fields);
      var actual = $validator.get('path.value');

      expect(actual).to.eql(expected);
    });

    it('is automatically set when passed in as defaultObject', function () {
      var $validator = this.model.at('validator');
      var defaultObject = {path: 'default.path'};
      var fields = {
        path: {
          'default': 'abc',
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = 'default.path';

      new Validator($validator, defaultObject, fields);
      var actual = $validator.get('path.value');

      expect(actual).to.eql(expected);
    });
  });
});

describe('Validation', function () {
  describe('Fields', function () {
    it('Required empty field does not validate', function (done) {
      var $validator = this.model.at('validator');
      var fields = {
        path: {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = false;

      var validator = new Validator($validator, fields);

      validator.validateAll(function (actual) {
        expect(actual).to.eql(expected);
        done();
      });
    });

    it('Required field with content does validate', function (done) {
      var $validator = this.model.at('validator');
      var fields = {
        path: {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = true;

      var validator = new Validator($validator, fields);
      $validator.set('path.value', 'something');

      validator.validateAll(function (actual) {
        expect(actual).to.eql(expected);
        done();
      });
    });
  });

  describe('Origin', function () {
    it('Origin copies it\'s data on setup', function () {
      var $validator = this.model.at('validator');
      var $origin = this.model.at('collection.1');
      var fields = {
        'c.d': {
          validations: [
            {
              rule: 'required'
            }
          ]
        }
      };
      var expected = 'a';

      new Validator($validator, $origin, fields);
      var actual = $validator.get('a.value');

      expect(actual).to.eql(expected);
    });
  });

  describe('Paths', function () {
    describe('Two levels', function () {
      it('Origin copies it\'s data on setup', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = 'd';

        new Validator($validator, $origin, fields);
        var actual = $validator.get('c.d.value');

        expect(actual).to.eql(expected);
      });

      it('Does not validate when required but non-existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does not validate when required but set empty', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        $validator.del('c.d.value');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required and set', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does get all values', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.defaultsDeep({c: {e: 'abc'}}, $origin.get());

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        var actual = validator.getValues();

        expect(actual).to.eql(expected);
      });

      it('Does commit values when valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: 'abc'}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit values when not valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep($origin.get());

        var validator = new Validator($validator, $origin, fields);
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit id', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.1');
        var fields = {
          'c.d': {},
          'c.e': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: 'abc'}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.value', 'abc');
        $validator.set('id.value', '2');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });
    });

    describe('Three levels', function () {
      it('Origin copies it\'s data on setup', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = 'f';

        new Validator($validator, $origin, fields);
        var actual = $validator.get('c.d.f.value');

        expect(actual).to.eql(expected);
      });

      it('Does not validate when required but non-existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required existing', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does not validate when required but set empty', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = false;

        var validator = new Validator($validator, $origin, fields);
        $validator.del('c.d.f.value');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does validate when required and set', function (done) {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        validator.validateAll(function (actual) {
          expect(actual).to.eql(expected);
          done();
        });
      });

      it('Does get all values', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.defaultsDeep({c: {e: {f: 'abc'}}}, $origin.get());

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        var actual = validator.getValues();

        expect(actual).to.eql(expected);
      });

      it('Does commit values when valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: {f: 'abc'}}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit values when not valid', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep($origin.get());

        var validator = new Validator($validator, $origin, fields);
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does not commit id', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = _.cloneDeep(_.defaultsDeep({c: {e: {f: 'abc'}}}, $origin.get()));

        var validator = new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        $validator.set('id.value', '3');
        validator.commit();
        var actual = $origin.get();

        expect(actual).to.eql(expected);
      });

      it('Does set changed property on subpath value changes', function () {
        var $validator = this.model.at('validator');
        var $origin = this.model.at('collection.2');
        var fields = {
          'c.d.f': {},
          'c.e.f': {
            validations: [
              {
                rule: 'required'
              }
            ]
          }
        };
        var expected = true;

        new Validator($validator, $origin, fields);
        $validator.set('c.e.f.value', 'abc');
        var actual = $validator.get('hasChangedFields');

        expect(actual).to.eql(expected);
      });
    });
  });
});
<|endoftext|>


<commit_before>/**
 * Visualization of genes, including exons and coding regions.
 * @flow
 */
'use strict';

var React = require('./react-shim'),
    _ = require('underscore'),
    d3 = require('d3'),
    shallowEquals = require('shallow-equals'),
    types = require('./react-types'),
    bedtools = require('./bedtools'),
    Interval = require('./Interval'),
    d3utils = require('./d3utils'),
    ContigInterval = require('./ContigInterval'),
    dataCanvas = require('./data-canvas');

// D3 function to hide overlapping elements in a selection.
// nb: this is O(n^2) in the number of transcripts on-screen.
// TODO: move into a d3utils module
var PADDING = 10;  // empty pixels to require around each element.

function drawArrow(ctx: CanvasRenderingContext2D, clampedScale: (x: number)=>number, range: Interval, tipY: number) {
  var x1 = clampedScale(range.start),
      x2 = clampedScale(range.stop);
  if (x1 != x2) {
    var cx = (x1 + x2) / 2;
    ctx.beginPath();
    ctx.moveTo(cx + 4, tipY - 4);
    ctx.lineTo(cx, tipY);
    ctx.lineTo(cx + 4, tipY + 4);
    ctx.stroke();
  }
}

var GeneTrack = React.createClass({
  displayName: 'genes',
  propTypes: {
    range: types.GenomeRange.isRequired,
    source: React.PropTypes.object.isRequired,
    onRangeChange: React.PropTypes.func.isRequired,
  },
  getInitialState: function() {
    return {
      genes: ([]: Object[])  // TODO: import Gene type from BigBedDataSource
    };
  },
  render: function(): any {
    return
<canvas ref='canvas' />
;
  },
  componentDidMount: function() {
    var div = this.getDOMNode();

    // Visualize new reference data as it comes in from the network.
    this.props.source.on('newdata', () => {
      var range = this.props.range,
          ci = new ContigInterval(range.contig, range.start, range.stop);
      this.setState({
        genes: this.props.source.getGenesInRange(ci)
      });
    });

    this.updateVisualization();
  },

  getContext(): CanvasRenderingContext2D {
    var canvas = (this.refs.canvas.getDOMNode() : HTMLCanvasElement);
    // The typecast through `any` is because getContext could return a WebGL context.
    var ctx = ((canvas.getContext('2d') : any) : CanvasRenderingContext2D);
    return ctx;
  },
  getScale: function() {
    return d3utils.getTrackScale(this.props.range, this.props.width);
  },
  componentDidUpdate: function(prevProps: any, prevState: any) {
    if (!shallowEquals(prevProps, this.props) ||
        !shallowEquals(prevState, this.state)) {
      this.updateVisualization();
    }
  },
  updateVisualization: function() {
    var canvas = (this.refs.canvas.getDOMNode() : HTMLCanvasElement),
        width = this.props.width,
        height = this.props.height;

    // Hold off until height & width are known.
    if (width === 0) return;

    var scale = this.getScale(),
        // We can't clamp scale directly because of offsetPx.
        clampedScale = d3.scale.linear()
            .domain([scale.invert(0), scale.invert(width)])
            .range([0, width])
            .clamp(true);

    d3.select(canvas).attr({width, height});

    var ctx = dataCanvas.getDataContext(this.getContext());
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    var geneLineY = height / 4;
    this.state.genes.forEach(gene => {
      ctx.pushObject(gene);
      ctx.lineWidth = 1;
      ctx.strokeStyle = 'blue';
      ctx.fillStyle = 'blue';
      ctx.beginPath();
      ctx.moveTo(clampedScale(gene.position.start()), geneLineY);
      ctx.lineTo(clampedScale(gene.position.stop()), geneLineY);
      ctx.stroke();

      // TODO: only compute all these intervals when data becomes available.
      var exons = bedtools.splitCodingExons(gene.exons, gene.codingRegion);
      exons.forEach(exon => {
        ctx.fillRect(scale(exon.start),
                     geneLineY - 3 * (exon.isCoding ? 2 : 1),
                     scale(exon.stop + 1) - scale(exon.start),
                     6 * (exon.isCoding ? 2 : 1));
      });

      ctx.strokeStyle = 'blue';
      var introns = gene.position.interval.complementIntervals(gene.exons);
      introns.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY);
      });
      ctx.strokeStyle = 'white';
      ctx.lineWidth = 2;
      gene.exons.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY);
      });

      var p = gene.position,
          centerX = 0.5 * (clampedScale(p.start()) + clampedScale(p.stop()));
      ctx.fillText(gene.name || gene.id, centerX, geneLineY + 15);
      ctx.popObject();
    });
  }
});

module.exports = GeneTrack;
<commit_msg>respond to code review<commit_after>/**
 * Visualization of genes, including exons and coding regions.
 * @flow
 */
'use strict';

var React = require('./react-shim'),
    _ = require('underscore'),
    d3 = require('d3'),
    shallowEquals = require('shallow-equals'),
    types = require('./react-types'),
    bedtools = require('./bedtools'),
    Interval = require('./Interval'),
    d3utils = require('./d3utils'),
    ContigInterval = require('./ContigInterval'),
    dataCanvas = require('./data-canvas');

var ARROW_SIZE = 4;
var GENE_COLOR = 'blue';  // color of the gene line, exons, text, etc.
var GENE_COMPLEMENT_COLOR = 'white';  // a color visible on top of GENE_COLOR

var GENE_FONT = `'Helvetica Neue', Helvetica, Arial, sans-serif`;
var GENE_FONT_SIZE = 16;
var GENE_TEXT_PADDING = 5;  // space between bottom of coding exon & top of gene name

// Draw an arrow in the middle of the visible portion of range.
// TODO: right-facing arrows!
function drawArrow(ctx: CanvasRenderingContext2D, clampedScale: (x: number)=>number, range: Interval, tipY: number) {
  var x1 = clampedScale(range.start),
      x2 = clampedScale(range.stop);
  if (x1 != x2) {
    var cx = (x1 + x2) / 2;
    ctx.beginPath();
    ctx.moveTo(cx + ARROW_SIZE, tipY - ARROW_SIZE);
    ctx.lineTo(cx, tipY);
    ctx.lineTo(cx + ARROW_SIZE, tipY + ARROW_SIZE);
    ctx.stroke();
  }
}

var GeneTrack = React.createClass({
  displayName: 'genes',
  propTypes: {
    range: types.GenomeRange.isRequired,
    source: React.PropTypes.object.isRequired,
    onRangeChange: React.PropTypes.func.isRequired,
  },
  getInitialState: function() {
    return {
      genes: ([]: Object[])  // TODO: import Gene type from BigBedDataSource
    };
  },
  render: function(): any {
    return
<canvas ref='canvas' />
;
  },
  componentDidMount: function() {
    var div = this.getDOMNode();

    // Visualize new reference data as it comes in from the network.
    this.props.source.on('newdata', () => {
      var range = this.props.range,
          ci = new ContigInterval(range.contig, range.start, range.stop);
      this.setState({
        genes: this.props.source.getGenesInRange(ci)
      });
    });

    this.updateVisualization();
  },

  getContext(): CanvasRenderingContext2D {
    var canvas = (this.refs.canvas.getDOMNode() : HTMLCanvasElement);
    // The typecast through `any` is because getContext could return a WebGL context.
    var ctx = ((canvas.getContext('2d') : any) : CanvasRenderingContext2D);
    return ctx;
  },
  getScale: function() {
    return d3utils.getTrackScale(this.props.range, this.props.width);
  },
  componentDidUpdate: function(prevProps: any, prevState: any) {
    if (!shallowEquals(prevProps, this.props) ||
        !shallowEquals(prevState, this.state)) {
      this.updateVisualization();
    }
  },
  updateVisualization: function() {
    var canvas = (this.refs.canvas.getDOMNode() : HTMLCanvasElement),
        width = this.props.width,
        height = this.props.height;

    // Hold off until height & width are known.
    if (width === 0) return;

    var scale = this.getScale(),
        // We can't clamp scale directly because of offsetPx.
        clampedScale = d3.scale.linear()
            .domain([scale.invert(0), scale.invert(width)])
            .range([0, width])
            .clamp(true);

    d3.select(canvas).attr({width, height});

    var ctx = dataCanvas.getDataContext(this.getContext());
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    var geneLineY = height / 4;
    this.state.genes.forEach(gene => {
      ctx.pushObject(gene);
      ctx.lineWidth = 1;
      ctx.strokeStyle = GENE_COLOR;
      ctx.fillStyle = GENE_COLOR;
      ctx.beginPath();
      ctx.moveTo(clampedScale(gene.position.start()), geneLineY);
      ctx.lineTo(clampedScale(gene.position.stop()), geneLineY);
      ctx.stroke();

      // TODO: only compute all these intervals when data becomes available.
      var exons = bedtools.splitCodingExons(gene.exons, gene.codingRegion);
      exons.forEach(exon => {
        ctx.fillRect(scale(exon.start),
                     geneLineY - 3 * (exon.isCoding ? 2 : 1),
                     scale(exon.stop + 1) - scale(exon.start),
                     6 * (exon.isCoding ? 2 : 1));
      });

      ctx.strokeStyle = GENE_COMPLEMENT_COLOR;
      var introns = gene.position.interval.complementIntervals(gene.exons);
      introns.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY);
      });
      ctx.strokeStyle = GENE_COMPLEMENT_COLOR;
      ctx.lineWidth = 2;
      gene.exons.forEach(range => {
        drawArrow(ctx, clampedScale, range, geneLineY);
      });

      var p = gene.position,
          centerX = 0.5 * (clampedScale(p.start()) + clampedScale(p.stop()));
      ctx.font = `${GENE_FONT_SIZE}px ${GENE_FONT}`;
      ctx.textAlign = 'center';
      ctx.fillText(gene.name || gene.id,
                   centerX,
                   geneLineY + GENE_FONT_SIZE + GENE_TEXT_PADDING);
      ctx.popObject();
    });
  }
});

module.exports = GeneTrack;
<|endoftext|>


<commit_before>import nock from 'nock';
import request from 'supertest';
import slackin from '../lib/index';

describe('slackin', () => {
  describe('POST /invite', () => {
    beforeEach(() => {
      nock('https://myorg.slack.com')
        .get('/api/users.list')
        .query({token: 'mytoken', presence: '1'})
        .query({token: 'mytoken'})
        .reply(200, {
          ok: true,
          members: [{}]
        });

      nock('https://myorg.slack.com')
        .get('/api/channels.list?token=mytoken')
        .reply(200, {
          ok: true,
          channels: [{}]
        });

      nock('https://myorg.slack.com')
        .get('/api/team.info?token=mytoken')
        .reply(200, {
          ok: true,
          team: {icon: {}}
        })
    });

    it("returns success for a successful invite", (done) => {
      let opts = {
        token: 'mytoken',
        org: 'myorg'
      };

      // TODO simplify mocking
      nock(`https://${opts.org}.slack.com`)
        .post('/api/users.admin.invite')
        .reply(200, { ok: true });

      let app = slackin(opts);

      request(app)
        .post('/invite')
        .send({ email: 'foo@example.com' })
        .expect('Content-Type', /json/)
        .expect(200, { msg: 'success' })
        .end(done);
    });

    it("returns a failure for a failure message", (done) => {
      let opts = {
        token: 'mytoken',
        org: 'myorg'
      };

      // TODO simplify mocking
      nock(`https://${opts.org}.slack.com`)
        .post('/api/users.admin.invite')
        .reply(200, {
          ok: false,
          error: "other error"
        });

      let app = slackin(opts);

      request(app)
        .post('/invite')
        .send({ email: 'foo@example.com' })
        .expect('Content-Type', /json/)
        .expect(400, { msg: "other error" })
        .end(done);
    });
  });
});
<commit_msg>Fix test for successful invite<commit_after>import nock from 'nock';
import request from 'supertest';
import slackin from '../lib/index';

describe('slackin', () => {
  describe('POST /invite', () => {
    beforeEach(() => {
      nock('https://myorg.slack.com')
        .get('/api/users.list')
        .query({token: 'mytoken', presence: '1'})
        .query({token: 'mytoken'})
        .reply(200, {
          ok: true,
          members: [{}]
        });

      nock('https://myorg.slack.com')
        .get('/api/channels.list?token=mytoken')
        .reply(200, {
          ok: true,
          channels: [{}]
        });

      nock('https://myorg.slack.com')
        .get('/api/team.info?token=mytoken')
        .reply(200, {
          ok: true,
          team: {icon: {}}
        })
    });

    it("returns success for a successful invite", (done) => {
      let opts = {
        token: 'mytoken',
        org: 'myorg'
      };

      // TODO simplify mocking
      nock(`https://${opts.org}.slack.com`)
        .post('/api/users.admin.invite')
        .reply(200, { ok: true });

      let app = slackin(opts);

      request(app)
        .post('/invite')
        .send({ email: 'foo@example.com' })
        .expect('Content-Type', /json/)
        .expect(200, { msg: 'WOOT. Check your email!' })
        .end(done);
    });

    it("returns a failure for a failure message", (done) => {
      let opts = {
        token: 'mytoken',
        org: 'myorg'
      };

      // TODO simplify mocking
      nock(`https://${opts.org}.slack.com`)
        .post('/api/users.admin.invite')
        .reply(200, {
          ok: false,
          error: "other error"
        });

      let app = slackin(opts);

      request(app)
        .post('/invite')
        .send({ email: 'foo@example.com' })
        .expect('Content-Type', /json/)
        .expect(400, { msg: "other error" })
        .end(done);
    });
  });
});
<|endoftext|>


<commit_before>var util = require('./util');
var nodemailer = require('nodemailer');
var sesTransport = require('nodemailer-ses-transport');

module.exports = function(app) {
	app.get('/contacts', function (req, res) {
		var query = util.dbQuery(req.query || {});
		app.settings.db.contacts.find(query, function(err, contacts) {

			res.status(200).send(contacts);
		});
	});

	app.get('/contacts/:contactsId', function (req, res) {
		var id = parseInt(req.params.contactId);
		if(isNaN(id)){
	        res.status(400).send({error:'id must be numeric'});
	        return;
		}

		app.settings.db.contacts.findOne(id, function(err, contact) {
			if(!contact){
				res.status(404).send({error:'contact with id ' + id + ' does must exists'})
			}
			res.status(200).send(contact);
		});

	});

	app.post('/contacts', function (req, res) {
		var contact = req.body;

		//name validation
		if(!contact.name){
			res.status(400).send({error:'name is required'});
			return;
		}
		if(contact.name.length < 4){
			res.status(404).send({error:'contact name must have at least 4 characters'});
			return;
		}
		if(contact.name.length > 100){
			res.status(404).send({error:'contact name must have less than 100 characters'});
			return;
		}

		//phone validation
		// if (!contact.phone) {
		// 	res.status(400).send({error:'phone is required'});
		// }

		if(contact.phone.length > 20){
			res.status(404).send({error:'contact phone must have less than 20 characters'});
			return;
		}
		//email validation
		if(!contact.email){
			res.status(400).send({error:'contact email is required'});
			return;
		}
		if(contact.email.length > 100){
			res.status(404).send({error:'contact email must have less than 100 characters'});
			return;
		}

		//text validation
		if(!contact.text){
			res.status(400).send({error:'text contact is required'});
			return;
		}

		//ip validation
		if (contact.ip && contact.ip.length < 7) {
			res.status(404).send({error:'contact ip must have at least 7 characters'});
			return;
		}

		if (contact.ip && contact.ip.length > 15) {
			res.status(404).send({error:'contact ip must have less than 15 characters'});
			return;
		}

		app.settings.db.contacts.save(contact, function(err, result) {

			res.status(200).send(result);

			var transporter = nodemailer.createTransport(sesTransport({
				accessKeyId: process.env.AWS_ACCESS_KEY_ID,
				secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
				region: 'eu-west-1'
			}));

	    	// setup e-mail data with unicode symbols
			var mailOptions = {
			    from: '"Contacto Chusrodriguez" <contacto@tallerchusrodriguez.com>', // sender address
			    replyTo: contact.email,
			    to: 'marta.suarez.rgz@gmail.com', // list of receivers
			    subject: 'Solicitud de contacto', // Subject line
			    html: 'El usuario ' + contact.name + ' te ha escrito un mensaje:

' + contact.text // html body
			};

			var mailOptionsClient = {
			    from: '"Contacto Chusrodriguez" <no-reply@tallerchusrodriguez.com>', // sender address
			    replyTo: contact.email,
			    to: 'contact.email', // list of receivers
			    subject: 'Hemos recibido su solicitud', // Subject line
			    html: '' + contact.name + ',

Gacias por ponerte en contacto con nosotros.

Hemos recibido tu mensaje y pronto nos pondremos en contacto contigo.

Este ha sido tu mensaje:

' + contact.text + '

Un saludo,
Equipo Chus Rodriguez.

NOTA:Este mensaje se ha generado automáticamente. Por favor, no responda a este correo.' // html body
			};

			// send mail with defined transport object
			transporter.sendMail(mailOptions, function(error, info){
			    if(error){
			        return console.log(error);
			    }
			    console.log('Message sent: ' + info.response);
			});

			transporter.sendMail(mailOptionsClient, function(error, info){
			    if(error){
			        return console.log(error);
			    }
			    console.log('Message sent: ' + info.response);
			});

		});
	});

	app.put('/contacts/:contactId', function (req, res) {
		var contact = req.body;

		//id validation
		var id = parseInt(req.params.contactId);
		if(isNaN(id)){
	        res.status(400).send({error:'id must be numeric'});
	        return;
		}

		//name validation
		if(!contact.name){
			res.status(400).send({error:'name is required'});
			return;
		}
		if(contact.name.length < 4){
			res.status(404).send({error:'contact name must have at least 4 characters'});
			return;
		}
		if(contact.name.length > 100){
			res.status(404).send({error:'contact name must have less than 100 characters'});
			return;
		}

		//description validation
		if(contact.description.length < 4){
			res.status(404).send({error:'contact description must have at least 4 characters'});
			return;
		}
		if(contact.description.length > 500){
			res.status(404).send({error:'contact description must have less than 500 characters'});
			return;
		}

		//contact_photo validation
		if(!contact.contact_photo){
			res.status(400).send({error:'image_url contact is required'});
			return;
		}
		if(contact.contact_photo.length > 255){
			res.status(404).send({error:'contact name must have less than 255 characters'});
			return;
		}

		app.settings.db.contacts.update(contact, function(err, result) {

			res.status(200).send(result);
		});

	});

	app.delete('/contacts/:contactId', function (req, res) {
		var contact = req.body;

		//id validation
		var id = parseInt(req.params.contactId);
		if(isNaN(id)){
	        res.status(400).send({error:'id must be numeric'});
	        return;
		}

		app.settings.db.contacts.destroy({id: id}, function(err, result) {
		res.status(200).send({status: "ok"});

		});

	});
};
<commit_msg>fixing email errors<commit_after>var util = require('./util');
var nodemailer = require('nodemailer');
var sesTransport = require('nodemailer-ses-transport');

module.exports = function(app) {
	app.get('/contacts', function (req, res) {
		var query = util.dbQuery(req.query || {});
		app.settings.db.contacts.find(query, function(err, contacts) {

			res.status(200).send(contacts);
		});
	});

	app.get('/contacts/:contactsId', function (req, res) {
		var id = parseInt(req.params.contactId);
		if(isNaN(id)){
	        res.status(400).send({error:'id must be numeric'});
	        return;
		}

		app.settings.db.contacts.findOne(id, function(err, contact) {
			if(!contact){
				res.status(404).send({error:'contact with id ' + id + ' does must exists'})
			}
			res.status(200).send(contact);
		});

	});

	app.post('/contacts', function (req, res) {
		var contact = req.body;

		//name validation
		if(!contact.name){
			res.status(400).send({error:'name is required'});
			return;
		}
		if(contact.name.length < 4){
			res.status(404).send({error:'contact name must have at least 4 characters'});
			return;
		}
		if(contact.name.length > 100){
			res.status(404).send({error:'contact name must have less than 100 characters'});
			return;
		}

		//phone validation
		// if (!contact.phone) {
		// 	res.status(400).send({error:'phone is required'});
		// }

		if(contact.phone.length > 20){
			res.status(404).send({error:'contact phone must have less than 20 characters'});
			return;
		}
		//email validation
		if(!contact.email){
			res.status(400).send({error:'contact email is required'});
			return;
		}
		if(contact.email.length > 100){
			res.status(404).send({error:'contact email must have less than 100 characters'});
			return;
		}

		//text validation
		if(!contact.text){
			res.status(400).send({error:'text contact is required'});
			return;
		}

		//ip validation
		if (contact.ip && contact.ip.length < 7) {
			res.status(404).send({error:'contact ip must have at least 7 characters'});
			return;
		}

		if (contact.ip && contact.ip.length > 15) {
			res.status(404).send({error:'contact ip must have less than 15 characters'});
			return;
		}

		app.settings.db.contacts.save(contact, function(err, result) {

			res.status(200).send(result);

			var transporter = nodemailer.createTransport(sesTransport({
				accessKeyId: process.env.AWS_ACCESS_KEY_ID,
				secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
				region: 'eu-west-1'
			}));

	    	// setup e-mail data with unicode symbols
			var mailOptions = {
			    from: '"Contacto Chusrodriguez" <contacto@tallerchusrodriguez.com>', // sender address
			    replyTo: contact.email,
			    to: 'marta.suarez.rgz@gmail.com', // list of receivers
			    subject: 'Solicitud de contacto', // Subject line
			    html: 'El usuario ' + contact.name + ' te ha escrito un mensaje:

' + contact.text // html body
			};

			var mailOptionsClient = {
			    from: '"Contacto Chusrodriguez" <no-reply@tallerchusrodriguez.com>', // sender address
			    replyTo: contact.email,
			    to: contact.email, // list of receivers
			    subject: 'Hemos recibido su solicitud', // Subject line
			    html: '' + contact.name + ',

Gacias por ponerte en contacto con nosotros.

Hemos recibido tu mensaje y pronto nos pondremos en contacto contigo.

Este ha sido tu mensaje:

' + contact.text + '

Un saludo,
Equipo Chus Rodriguez.

NOTA:Este mensaje se ha generado automáticamente. Por favor, no responda a este correo.' // html body
			};

			// send mail with defined transport object
			transporter.sendMail(mailOptions, function(error, info){
			    if(error){
			        return console.log(error);
			    }
			    console.log('Message sent: ' + info.response);
			});

			transporter.sendMail(mailOptionsClient, function(error, info){
			    if(error){
			        return console.log(error);
			    }
			    console.log('Message sent: ' + info.response);
			});

		});
	});

	app.put('/contacts/:contactId', function (req, res) {
		var contact = req.body;

		//id validation
		var id = parseInt(req.params.contactId);
		if(isNaN(id)){
	        res.status(400).send({error:'id must be numeric'});
	        return;
		}

		//name validation
		if(!contact.name){
			res.status(400).send({error:'name is required'});
			return;
		}
		if(contact.name.length < 4){
			res.status(404).send({error:'contact name must have at least 4 characters'});
			return;
		}
		if(contact.name.length > 100){
			res.status(404).send({error:'contact name must have less than 100 characters'});
			return;
		}

		//description validation
		if(contact.description.length < 4){
			res.status(404).send({error:'contact description must have at least 4 characters'});
			return;
		}
		if(contact.description.length > 500){
			res.status(404).send({error:'contact description must have less than 500 characters'});
			return;
		}

		//contact_photo validation
		if(!contact.contact_photo){
			res.status(400).send({error:'image_url contact is required'});
			return;
		}
		if(contact.contact_photo.length > 255){
			res.status(404).send({error:'contact name must have less than 255 characters'});
			return;
		}

		app.settings.db.contacts.update(contact, function(err, result) {

			res.status(200).send(result);
		});

	});

	app.delete('/contacts/:contactId', function (req, res) {
		var contact = req.body;

		//id validation
		var id = parseInt(req.params.contactId);
		if(isNaN(id)){
	        res.status(400).send({error:'id must be numeric'});
	        return;
		}

		app.settings.db.contacts.destroy({id: id}, function(err, result) {
		res.status(200).send({status: "ok"});

		});

	});
};
<|endoftext|>


<commit_before>var test = require('tape')

var events = require('../events')

function isObject(o) {
  return o && 'object' === typeof o
}

function isFunction (f) {
  return 'function' === typeof f
}

function is (t, actual, expected, path) {
  if(isFunction(expected))
    expected.call(actual, path.concat(k))
  else t.equal(expected, actual, 'expected '+path.join('.')+' to equal:'+actual)
}

function has (t, actual, expected, path) {
  path = path || []

  if(!(isObject(actual))) return is(t, actual, expected, path)

  if(!isObject(expected))
    return t.fail('expected object at path:'+path.join('.'))

  for(var k in expected)
    has(t, actual[k], expected[k], path.concat(k))
}

test('initialize, connect to new peer', function (t) {

  var state = events.initialize()

  state = events.connect(state, {id: 'alice'})
  state = events.peerClock(state, {id: 'alice', value: {}})

  has(t, state, {
    peers: {
      alice: { clock: {}, msgs: [], notes: {}, replicating: {} },
    }
  })

  state = events.clock(state, {})

  state = events.follow(state, {id: 'alice', value: true})

  t.deepEqual(state.follows, {alice: true})
  console.log(state.peers.alice)

  has(t, state.peers.alice, {
    clock: {},
    notes: { alice: 0 },
    replicating: {alice: {rx: true}}
  }, ['state', 'peers', 'alice'])

  //lets say we send the note

  state = events.notes(state, {id: 'alice', value: {alice: 2}})
  has(t, state, {
    clock: {},
    follows: {alice: true},
    peers: {
      alice: {
        clock: {alice: 2},
        replicating: {
          alice: {
            rx: true, tx: true
          }
        }
      }
    }
  })

  var msg = {author: 'alice', sequence: 1, content: {}}
  state = events.receive(state, {id: 'alice', value:msg})

  has(t, state, {
    peers: {
      alice: {
        clock: {alice: 2},
        replicating: {
          alice: {
            rx: true, tx: true
          }
        }
      }
    },
    receive: [msg],
  })

  var msg = state.receive.shift()

  state = events.append(state, msg)

  console.log(state)

  has(t, state, {
    clock: {alice: 1}
  })

  var msg2 = {author: 'alice', sequence: 2, content: {}}
  state = events.receive(state, {id: 'alice', value:msg2})
  state = events.append(state, state.receive.shift())

  has(t, state, {
    clock: {alice: 2}
  })

  var msg3 = {author: 'alice', sequence: 3, content: {}}
  state = events.receive(state, {id: 'alice', value:msg3})
  state = events.append(state, state.receive.shift())

  has(t, state, {
    clock: {alice: 3}
  })

  t.end()

})

test('connect to two peers, append message one send, one note', function (t) {

  var state = {
    clock: { alice: 1 },
    peers: {
      bob: {
        clock: { alice: 1 },
        msgs: [], retrive: [],
        replicating: {
          alice: {
            rx: true, tx: true, sent: 1, retrive: false
          }
        }
      },
      charles: {
        clock: {alice: 1},
        msgs: [], retrive: [],
        replicating: {
          alice: {
            rx: false, tx: false, sent: 1, retrive: false
          }
        }
      }
    }
  }

  var msg = {author: 'alice', sequence: 2, content: {}}
  state = events.append(state, msg)

  has(t, state, {
    clock: { alice: 2 },
    peers: {
      bob: {
        clock: { alice: 1 },
        msgs: [msg],
        retrive: [],
        replicating: {
          alice: {
            rx: true, tx: true, sent: 2, retrive: false
          }
        }
      },
      charles: {
        clock: { alice: 1 },
        notes: { alice: -3 },
        retrive: [],
        replicating: {
          alice: {
            rx: false, tx: false, sent: 1
          }
        }
      }
    }
  })

  t.end()

})

test('reply to any clock they send, 1', function (t) {
  var state = {
    clock: { alice: 3, bob: 2, charles: 3 },
    follows: { alice: true, bob: true, charles: true, darlene: false },
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, charles: 1}})
  t.deepEqual(state.peers.bob.notes, {bob: 2, charles: 3})

  state = events.notes(state, {id: 'bob', value: {alice: 3, darlene: 4}})

  //notes hasn't been sent, so this is merged with previous
  t.deepEqual(state.peers.bob.notes, {alice: ~3, bob: 2, charles: 3, darlene: -1})

  t.end()
})

test('reply to any clock they send, 2', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, charles: 1}})
  t.deepEqual(state.peers.bob.notes, {bob: 2})

  state = events.notes(state, {id: 'bob', value: {alice: 3}})

  //notes hasn't been sent, so this is merged with previous
  t.deepEqual(state.peers.bob.notes, {alice: ~3, bob: 2})

  state = events.follow(state, {id: 'charles',value: true})
  t.deepEqual(state.peers.bob.notes, {alice: ~3, bob: 2, charles: 0})

  t.end()
})

test('append when not in TX mode', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }
  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, charles: 1}})
  t.deepEqual(state.peers.bob.notes, {bob: 2})

  state = events.notes(state, {id: 'bob', value: {alice: ~3}})
  var rep = state.peers.bob.replicating.alice
  t.equal(rep.tx, false)
  t.equal(rep.sent, 3)

  console.log(state.peers.bob.replicating)

  state = events.append(state, {author: 'alice', sequence: 4, content: {}})
  t.deepEqual(state.peers.bob.notes, {bob: 2, alice: ~4})
  var rep = state.peers.bob.replicating.alice
  t.equal(rep.tx, false)
  t.equal(rep.sent, 3)

  state = events.notes(state, {id: 'bob', value: {alice: 3}})
  t.deepEqual(state.peers.bob.retrive, ['alice'])

  t.end()
})

test('note when not in RX mode', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {
      bob: {
        clock: {alice: 3, bob: 2},
        retrive: [],
        msgs: [],
        notes: null,
        replicating: {
          alice: {
            tx:false, rx: false, sent: 3
          }
        }
      }
    }
  }

  state = events.notes(state, {id: 'bob', value: {alice: ~5}})
  var rep = state.peers.bob.replicating.alice
//  t.equal(rep.tx, true)
  t.equal(rep.rx, true)
  t.equal(rep.sent, 5)
  t.deepEqual(state.peers.bob.notes, {alice: 3})

  console.log(state.peers.bob.replicating)

//  state = events.append(state, {author: 'alice', sequence: 4, content: {}})
//  t.deepEqual(state.peers.bob.notes, {bob: 2, alice: ~4})
//  var rep = state.peers.bob.replicating.alice
//  t.equal(rep.tx, false)
//  t.equal(rep.sent, 3)
//
  t.end()

})

test('note when value is not integer', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{}})

  t.deepEqual(state.peers.bob.clock, {})
  state = events.notes(state, {id: 'bob', value: {alice: true}})

  t.deepEqual(state.peers.bob.clock, {alice: -1})
  t.deepEqual(state.peers.bob.notes, {alice: 3, bob: 2})

  t.end()
})

test('test sends empty clock if nothing needed', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, bob: 2}})

  t.deepEqual(state.peers.bob.clock, {alice: 3, bob: 2})
  t.deepEqual(state.peers.bob.notes, {})

  //receive empty clock
  state = events.notes(state, {id: 'bob', value: {}})
  t.deepEqual(state.peers.bob.replicating, {})

  t.end()
})

<commit_msg>test edgecase I noticed from watching logs<commit_after>var test = require('tape')

var events = require('../events')

function isObject(o) {
  return o && 'object' === typeof o
}

function isFunction (f) {
  return 'function' === typeof f
}

function is (t, actual, expected, path) {
  if(isFunction(expected))
    expected.call(actual, path.concat(k))
  else t.equal(expected, actual, 'expected '+path.join('.')+' to equal:'+actual)
}

function has (t, actual, expected, path) {
  path = path || []

  if(!(isObject(actual))) return is(t, actual, expected, path)

  if(!isObject(expected))
    return t.fail('expected object at path:'+path.join('.'))

  for(var k in expected)
    has(t, actual[k], expected[k], path.concat(k))
}

test('initialize, connect to new peer', function (t) {

  var state = events.initialize()

  state = events.connect(state, {id: 'alice'})
  state = events.peerClock(state, {id: 'alice', value: {}})

  has(t, state, {
    peers: {
      alice: { clock: {}, msgs: [], notes: {}, replicating: {} },
    }
  })

  state = events.clock(state, {})

  state = events.follow(state, {id: 'alice', value: true})

  t.deepEqual(state.follows, {alice: true})
  console.log(state.peers.alice)

  has(t, state.peers.alice, {
    clock: {},
    notes: { alice: 0 },
    replicating: {alice: {rx: true}}
  }, ['state', 'peers', 'alice'])

  //lets say we send the note

  state = events.notes(state, {id: 'alice', value: {alice: 2}})
  has(t, state, {
    clock: {},
    follows: {alice: true},
    peers: {
      alice: {
        clock: {alice: 2},
        replicating: {
          alice: {
            rx: true, tx: true
          }
        }
      }
    }
  })

  var msg = {author: 'alice', sequence: 1, content: {}}
  state = events.receive(state, {id: 'alice', value:msg})

  has(t, state, {
    peers: {
      alice: {
        clock: {alice: 2},
        replicating: {
          alice: {
            rx: true, tx: true
          }
        }
      }
    },
    receive: [msg],
  })

  var msg = state.receive.shift()

  state = events.append(state, msg)

  console.log(state)

  has(t, state, {
    clock: {alice: 1}
  })

  var msg2 = {author: 'alice', sequence: 2, content: {}}
  state = events.receive(state, {id: 'alice', value:msg2})
  state = events.append(state, state.receive.shift())

  has(t, state, {
    clock: {alice: 2}
  })

  var msg3 = {author: 'alice', sequence: 3, content: {}}
  state = events.receive(state, {id: 'alice', value:msg3})
  state = events.append(state, state.receive.shift())

  has(t, state, {
    clock: {alice: 3}
  })

  t.end()

})

test('connect to two peers, append message one send, one note', function (t) {

  var state = {
    clock: { alice: 1 },
    peers: {
      bob: {
        clock: { alice: 1 },
        msgs: [], retrive: [],
        replicating: {
          alice: {
            rx: true, tx: true, sent: 1, retrive: false
          }
        }
      },
      charles: {
        clock: {alice: 1},
        msgs: [], retrive: [],
        replicating: {
          alice: {
            rx: false, tx: false, sent: 1, retrive: false
          }
        }
      }
    }
  }

  var msg = {author: 'alice', sequence: 2, content: {}}
  state = events.append(state, msg)

  has(t, state, {
    clock: { alice: 2 },
    peers: {
      bob: {
        clock: { alice: 1 },
        msgs: [msg],
        retrive: [],
        replicating: {
          alice: {
            rx: true, tx: true, sent: 2, retrive: false
          }
        }
      },
      charles: {
        clock: { alice: 1 },
        notes: { alice: -3 },
        retrive: [],
        replicating: {
          alice: {
            rx: false, tx: false, sent: 1
          }
        }
      }
    }
  })

  t.end()

})

test('reply to any clock they send, 1', function (t) {
  var state = {
    clock: { alice: 3, bob: 2, charles: 3 },
    follows: { alice: true, bob: true, charles: true, darlene: false },
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, charles: 1}})
  t.deepEqual(state.peers.bob.notes, {bob: 2, charles: 3})

  state = events.notes(state, {id: 'bob', value: {alice: 3, darlene: 4}})

  //notes hasn't been sent, so this is merged with previous
  t.deepEqual(state.peers.bob.notes, {alice: ~3, bob: 2, charles: 3, darlene: -1})

  t.end()
})

test('reply to any clock they send, 2', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, charles: 1}})
  t.deepEqual(state.peers.bob.notes, {bob: 2})

  state = events.notes(state, {id: 'bob', value: {alice: 3}})

  //notes hasn't been sent, so this is merged with previous
  t.deepEqual(state.peers.bob.notes, {alice: ~3, bob: 2})

  state = events.follow(state, {id: 'charles',value: true})
  t.deepEqual(state.peers.bob.notes, {alice: ~3, bob: 2, charles: 0})

  t.end()
})

test('append when not in TX mode', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }
  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, charles: 1}})
  t.deepEqual(state.peers.bob.notes, {bob: 2})

  state = events.notes(state, {id: 'bob', value: {alice: ~3}})
  var rep = state.peers.bob.replicating.alice
  t.equal(rep.tx, false)
  t.equal(rep.sent, 3)

  console.log(state.peers.bob.replicating)

  state = events.append(state, {author: 'alice', sequence: 4, content: {}})
  t.deepEqual(state.peers.bob.notes, {bob: 2, alice: ~4})
  var rep = state.peers.bob.replicating.alice
  t.equal(rep.tx, false)
  t.equal(rep.sent, 3)

  state = events.notes(state, {id: 'bob', value: {alice: 3}})
  t.deepEqual(state.peers.bob.retrive, ['alice'])

  t.end()
})

test('note when not in RX mode', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {
      bob: {
        clock: {alice: 3, bob: 2},
        retrive: [],
        msgs: [],
        notes: null,
        replicating: {
          alice: {
            tx:false, rx: false, sent: 3
          }
        }
      }
    }
  }

  state = events.notes(state, {id: 'bob', value: {alice: ~5}})
  var rep = state.peers.bob.replicating.alice
//  t.equal(rep.tx, true)
  t.equal(rep.rx, true)
  t.equal(rep.sent, 5)
  t.deepEqual(state.peers.bob.notes, {alice: 3})

  console.log(state.peers.bob.replicating)

//  state = events.append(state, {author: 'alice', sequence: 4, content: {}})
//  t.deepEqual(state.peers.bob.notes, {bob: 2, alice: ~4})
//  var rep = state.peers.bob.replicating.alice
//  t.equal(rep.tx, false)
//  t.equal(rep.sent, 3)
//
  t.end()

})

test('note when value is not integer', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{}})

  t.deepEqual(state.peers.bob.clock, {})
  state = events.notes(state, {id: 'bob', value: {alice: true}})

  t.deepEqual(state.peers.bob.clock, {alice: -1})
  t.deepEqual(state.peers.bob.notes, {alice: 3, bob: 2})

  t.end()
})

test('test sends empty clock if nothing needed', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, bob: 2}})

  t.deepEqual(state.peers.bob.clock, {alice: 3, bob: 2})
  t.deepEqual(state.peers.bob.notes, {})

  //receive empty clock
  state = events.notes(state, {id: 'bob', value: {}})
  t.deepEqual(state.peers.bob.replicating, {})

  t.end()
})

test('connects in sync then another message', function (t) {
  var state = {
    clock: { alice: 3, bob: 2},
    follows: { alice: true, bob: true},
    peers: {}
  }

  state = events.connect(state, {id: 'bob'})
  state = events.peerClock(state, {id: 'bob', value:{alice: 3, bob: 2}})

  t.deepEqual(state.peers.bob.clock, {alice: 3, bob: 2})
  t.deepEqual(state.peers.bob.notes, {})

  //receive empty clock
  state = events.notes(state, {id: 'bob', value: {}})
  t.deepEqual(state.peers.bob.replicating, {})

  state = events.append(state, {author: 'alice', sequence: 4, content: {}})
  t.deepEqual(state.peers.bob.notes, {alice: ~4})

  t.end()
})
<|endoftext|>


<commit_before>const baseSpecs = [['A', 'Automapa', 'car'], ['T', 'Turistická', '!icon-hiking'], ['C', 'Cyklomapa', 'bicycle'], ['K', 'Lyžiarska', '!icon-skier-skiing']];

export const baseLayers = [
  ...baseSpecs.map(([type, name, icon]) => ({
    name,
    type,
    icon,
    url: `//{s}.freemap.sk/${type}/{z}/{x}/{y}.{tileFormat}`,
    attribution: 'mapa © Freemap Slovakia, dáta © prispievatelia OpenStreetMap',
    minZoom: 8,
    maxNativeZoom: 16,
    key: type.toLowerCase(),
  })),
  {
    name: 'Satelitná',
    type: 'S',
    icon: 'plane',
    minZoom: 0,
    maxNativeZoom: 18,
    key: 's',
  },
  {
    name: 'OpenStreetMap',
    type: 'O',
    icon: 'globe',
    url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 19,
    // showOnlyInExpertMode: true,
    attribution: 'dáta © prispievatelia OpenStreetMap contributors',
    key: 'o',
  },
  {
    name: 'mtbmap.cz',
    type: 'M',
    url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 18,
    showOnlyInExpertMode: true,
    attribution: 'mapa © Martin Tesař, dáta © prispievatelia OpenStreetMap',
    key: 'm',
  },
  // {
  //   name: 'Infomapa',
  //   type: 'i',
  //   icon: 'info',
  //   url: 'http://{s}.infomapa.sk/0/{z}/{x}/{y}',
  //   minZoom: 8,
  //   maxNativeZoom: 18,
  //   showOnlyInExpertMode: true,
  //   attribution: 'mapa © 2016 www.infomapa.sk, dáta © prispievatelia OpenStreetMap',
  //   key: 'i',
  // },
  // {
  //   name: 'Infomapa ČB',
  //   type: 'j',
  //   icon: 'info',
  //   url: 'http://{s}.infomapa.sk/1/{z}/{x}/{y}',
  //   minZoom: 8,
  //   maxNativeZoom: 18,
  //   showOnlyInExpertMode: true,
  //   attribution: 'mapa © 2016 www.infomapa.sk, dáta © prispievatelia OpenStreetMap',
  //   key: 'j',
  // },
];

export const overlayLayers = [
  {
    name: 'Fotografie',
    type: 'I',
    icon: 'picture-o',
    minZoom: 0,
    key: 'f',
    zIndex: 3,
    attribution: 'fotografie © CC-BY-SA', // FIXME it is ignored
  },
  // {
  //   name: 'Lesné cesty NLC 2016',
  //   type: 'n',
  //   icon: 'tree',
  //   url: '//tiles.freemap.sk/nlc2016/{z}/{x}/{y}.png',
  //   attribution: '© NLC Zvolen',
  //   minZoom: 11,
  //   maxNativeZoom: 15,
  //   key: 'n',
  //   zIndex: 2,
  // },
  // {
  //   name: 'Lesné cesty NLC',
  //   type: 'N',
  //   icon: 'tree',
  //   url: 'http://gpsteam.eu/cache/nlcml/{z}/{x}/{y}.png',
  //   attribution: '© NLC Zvolen',
  //   minZoom: 14,
  //   maxNativeZoom: 16,
  //   key: 'N',
  //   zIndex: 2,
  // },
  // {
  //   name: 'Strava heatmap',
  //   type: 's',
  //   url: '//heatmap-external-{s}.strava.com/tiles/ride/bluered/{z}/{x}/{y}.png?px=256',
  //   attribution: '© Strava',
  //   minZoom: 0,
  //   maxNativeZoom: 16,
  //   key: 'h',
  //   showOnlyInExpertMode: true,
  //   zIndex: 2,
  // },
  {
    name: 'OSM GPS stopy',
    type: 'g',
    icon: '!icon-gps-device',
    url: '//gps-{s}.tile.openstreetmap.org/lines/{z}/{x}/{y}.png',
    attribution: '© prispievatelia OpenStreetMap',
    minZoom: 0,
    maxNativeZoom: 20,
    key: 'g',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'Turistické trasy',
    type: 't',
    icon: '!icon-hiking',
    url: '//tiles.freemap.sk/trails/{z}/{x}/{y}.png',
    attribution: '© prispievatelia OpenStreetMap',
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'T',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'Cyklotrasy',
    type: 'c',
    icon: 'bicycle',
    url: '//tiles.freemap.sk/cycle/{z}/{x}/{y}.png',
    attribution: '© prispievatelia OpenStreetMap',
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'C',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'Render. klienti',
    type: 'r',
    icon: 'pencil-square-o',
    url: '//old.freemap.sk/layers/renderedby/?/{z}/{x}/{y}',
    minZoom: 8,
    maxNativeZoom: 12,
    key: 'R',
    showOnlyInExpertMode: true,
    zIndex: 4,
  },
];

// http://tile.mtbmap.cz/mtbmap_tiles/11/1135/705.png
// http://mtbmap.cz/
// 3 - 18
<commit_msg>added additional map layers<commit_after>const baseSpecs = [['A', 'Automapa', 'car'], ['T', 'Turistická', '!icon-hiking'], ['C', 'Cyklomapa', 'bicycle'], ['K', 'Lyžiarska', '!icon-skier-skiing']];

export const baseLayers = [
  ...baseSpecs.map(([type, name, icon]) => ({
    name,
    type,
    icon,
    url: `//{s}.freemap.sk/${type}/{z}/{x}/{y}.{tileFormat}`,
    attribution: 'mapa © Freemap Slovakia, dáta © prispievatelia OpenStreetMap',
    minZoom: 8,
    maxNativeZoom: 16,
    key: type.toLowerCase(),
  })),
  {
    name: 'Satelitná',
    type: 'S',
    icon: 'plane',
    minZoom: 0,
    maxNativeZoom: 18,
    key: 's',
  },
  {
    name: 'OpenStreetMap',
    type: 'O',
    icon: 'globe',
    url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 19,
    // showOnlyInExpertMode: true,
    attribution: 'dáta © prispievatelia OpenStreetMap contributors',
    key: 'o',
  },
  {
    name: 'mtbmap.cz',
    type: 'M',
    url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 18,
    icon: 'bicycle',
    showOnlyInExpertMode: true,
    attribution: 'mapa © Martin Tesař, dáta © prispievatelia OpenStreetMap',
    key: 'm',
  },
  {
    name: 'OpenTopoMap',
    type: 'p',
    url: '//tile.opentopomap.org/{z}/{x}/{y}.png',
    minZoom: 3,
    maxNativeZoom: 17,
    icon: 'tree',
    showOnlyInExpertMode: true,
    attribution: 'mapa © OpenTopoMap, dáta © prispievatelia OpenStreetMap, SRTM',
    key: 'p',
  },
  {
    name: 'Humanitárna',
    type: 'b',
    url: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 20,
    icon: 'medkit',
    showOnlyInExpertMode: true,
    attribution: 'mapa © Humanitárny tím OpenStreetMap, dáta © prispievatelia OpenStreetMap',
    key: 'b',
  },
  {
    name: 'Verejná doprava (ÖPNV)',
    type: 'd',
    url: '//tile.memomaps.de/tilegen/{z}/{x}/{y}.png',
    minZoom: 0,
    maxNativeZoom: 18,
    icon: 'bus',
    showOnlyInExpertMode: true,
    attribution: '© prispievatelia OpenStreetMap',
    key: 'd',
  },
  {
    name: 'Infomapa',
    type: 'i',
    icon: 'info',
    url: 'http://{s}.infomapa.sk/0/{z}/{x}/{y}',
    minZoom: 8,
    maxNativeZoom: 18,
    showOnlyInExpertMode: true,
    attribution: 'mapa © 2016 www.infomapa.sk, dáta © prispievatelia OpenStreetMap',
    key: 'i',
  },
  {
    name: 'Infomapa ČB',
    type: 'j',
    icon: 'info',
    url: 'http://{s}.infomapa.sk/1/{z}/{x}/{y}',
    minZoom: 8,
    maxNativeZoom: 18,
    showOnlyInExpertMode: true,
    attribution: 'mapa © 2016 www.infomapa.sk, dáta © prispievatelia OpenStreetMap',
    key: 'j',
  },
  {
    name: 'Historická',
    type: 'h',
    url: '//tms.freemap.sk/historicke/{z}/{x}/{y}.png',
    minNativeZoom: 12,
    maxNativeZoom: 12,
    icon: 'institution',
    showOnlyInExpertMode: true,
    key: 'h',
  },
];

export const overlayLayers = [
  {
    name: 'Fotografie',
    type: 'I',
    icon: 'picture-o',
    minZoom: 0,
    key: 'f',
    zIndex: 3,
    attribution: 'fotografie © CC-BY-SA', // FIXME it is ignored
  },
  // {
  //   name: 'Lesné cesty NLC 2016',
  //   type: 'n',
  //   icon: 'tree',
  //   url: '//tiles.freemap.sk/nlc2016/{z}/{x}/{y}.png',
  //   attribution: '© NLC Zvolen',
  //   minZoom: 11,
  //   maxNativeZoom: 15,
  //   key: 'n',
  //   zIndex: 2,
  // },
  // {
  //   name: 'Lesné cesty NLC',
  //   type: 'N',
  //   icon: 'tree',
  //   url: 'http://gpsteam.eu/cache/nlcml/{z}/{x}/{y}.png',
  //   attribution: '© NLC Zvolen',
  //   minZoom: 14,
  //   maxNativeZoom: 16,
  //   key: 'N',
  //   zIndex: 2,
  // },
  // {
  //   name: 'Strava heatmap',
  //   type: 's',
  //   url: '//heatmap-external-{s}.strava.com/tiles/ride/bluered/{z}/{x}/{y}.png?px=256',
  //   attribution: '© Strava',
  //   minZoom: 0,
  //   maxNativeZoom: 16,
  //   key: 'h',
  //   showOnlyInExpertMode: true,
  //   zIndex: 2,
  // },
  {
    name: 'OSM GPS stopy',
    type: 'g',
    icon: '!icon-gps-device',
    url: '//gps-{s}.tile.openstreetmap.org/lines/{z}/{x}/{y}.png',
    attribution: '© prispievatelia OpenStreetMap',
    minZoom: 0,
    maxNativeZoom: 20,
    key: 'g',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'Turistické trasy',
    type: 't',
    icon: '!icon-hiking',
    url: '//tiles.freemap.sk/trails/{z}/{x}/{y}.png',
    attribution: '© prispievatelia OpenStreetMap',
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'T',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'Cyklotrasy',
    type: 'c',
    icon: 'bicycle',
    url: '//tiles.freemap.sk/cycle/{z}/{x}/{y}.png',
    attribution: '© prispievatelia OpenStreetMap',
    minZoom: 8,
    maxNativeZoom: 16,
    key: 'C',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'OpenSnowMap',
    type: 'q',
    icon: 'snowflake-o',
    url: '//www.opensnowmap.org/pistes/{z}/{x}/{y}.png',
    attribution: '© OpenSnowMap.org',
    minZoom: 0,
    maxNativeZoom: 18,
    key: 'S',
    showOnlyInExpertMode: true,
    zIndex: 2,
  },
  {
    name: 'Render. klienti',
    type: 'r',
    icon: 'pencil-square-o',
    url: '//old.freemap.sk/layers/renderedby/?/{z}/{x}/{y}',
    minZoom: 8,
    maxNativeZoom: 12,
    key: 'R',
    showOnlyInExpertMode: true,
    zIndex: 4,
  },
];

// http://tile.mtbmap.cz/mtbmap_tiles/11/1135/705.png
// http://mtbmap.cz/
// 3 - 18
<|endoftext|>


<commit_before>(function ($) {

    window.ContentGallery = window.ContentGallery || {};

    var gallerySiteView = (function (gallery, animateSync) {
        var $imageContainer,
            $image,
            $thumbnails,
            $scrollLeft,
            $scrollRight,
            $choices,
            $thumbnailsView,
            $imageView,
            $thumbnailsContainer;

        var thumbnailWidth, maxOffset, imgSize, thumbnailSize, small;

        function checkScrollButtons(left) {
            if (left < 0)
                $scrollLeft.removeClass("content-gallery-inactive");
            else
                $scrollLeft.addClass("content-gallery-inactive");

            if (left > maxOffset)
                $scrollRight.removeClass("content-gallery-inactive");
            else
                $scrollRight.addClass("content-gallery-inactive");
        }

        function scrollToImage(index) {
            left = parseInt($thumbnails.css("left"));
            begin = thumbnailWidth * index;
            end = thumbnailWidth * (index + 1);

            if (left + begin >= 0 && left + end <= $thumbnailsContainer.width())
                return;

            if (left + begin < 0)
                newLeft = -begin;

            if (left + end > $thumbnailsContainer.width())
                newLeft = $thumbnailsContainer.width() - end;

            animateSync.safeAnimate($thumbnails, {left: newLeft}, null);
            checkScrollButtons(newLeft);
        }

        function setImage(index, callback) {
            img = gallery.getImage(index);
            if (!img) return;
            $choices.addClass("choice");
            $($choices[index]).removeClass("choice");
            if (small) {
                src = img.small_image;
                size = img.small_image_size;
            } else {
                src = img.image;
                size = img.image_size;
            }
            callback(size, src);
        }

        function setImageAnim(index) {
            setImage(index, function (size, src) {
                animateSync.safeAnimate($image, {width: 0, height: 0}, function () {
                    $image.attr("src", src);
                    animateSync.safeAnimate($image, {width: size.width, height: size.height}, null);
                });
            });
        }

        function setImageFast(index) {
            setImage(index, function (size, src) {
                $image.attr("src", src).css({width: size.width, height: size.height});
            });
        }

        function slideImage(index) {
            setImageAnim(index);
            scrollToImage(index);
        }

        function setThumbnailViewSize(imgSize, thumbnailSize) {
            $thumbnails.width(thumbnailWidth * gallery.count());

            container_width = Math.ceil(imgSize.width / thumbnailWidth) * thumbnailWidth;
            if (container_width > imgSize.width + 140)
                container_width -= thumbnailWidth;

            $thumbnailsView.width(container_width + 60);
            $choices.width(thumbnailSize.width)
                    .height(thumbnailSize.height)
                    .css("line-height", thumbnailSize.height + "px");
            $thumbnailsView.height(thumbnailSize.height + 2);
            $thumbnailsContainer.width(container_width);
        }

        function setImageHeight(height) {
            $imageView.height(height);
            $imageContainer.css({"line-height": height + "px"});
        }

        function nextImage() {
            animateSync.safeRun(function () {
                index = gallery.next();
                slideImage(index);
            });
        }

        function previousImage() {
            animateSync.safeRun(function () {
               index = gallery.prev();
               slideImage(index);
            });
        }

        function setImageByIndex(index) {
            animateSync.safeRun(function () {
                setImageAnim(index);
            });
        }

        function scrollLeft() {
            animateSync.safeRun(function () {
                left = parseInt($thumbnails.css("left"));
                if (left < 0) {
                    animateSync.safeAnimate($thumbnails, {left: "+=" + thumbnailWidth}, null);
                    checkScrollButtons(left + thumbnailWidth);
                }
            });
        }

        function scrollRight() {
            animateSync.safeRun(function () {
                left = parseInt($thumbnails.css("left"));
                if (left > maxOffset) {
                    animateSync.safeAnimate($thumbnails, {left: "-=" + thumbnailWidth}, null);
                    checkScrollButtons(left - thumbnailWidth);
                }
            });
        }

        function resize() {
            setThumbnailViewSize(imgSize, thumbnailSize);
            maxOffset = $thumbnailsContainer.width() - $thumbnails.width();

            checkScrollButtons(0);

            setImageHeight(imgSize.height);

            index = gallery.current();
            setImageFast(index);
        }

        function init() {
            $imageView = $("#content-gallery-image-view");
            $imageContainer = $(".content-gallery-image-container");
            $image = $(".content-gallery-image-container > img");
            $thumbnailsView = $("#content-gallery-thumbnails-view");
            $scrollLeft = $(".content-gallery-scroll-left");
            $scrollRight = $(".content-gallery-scroll-right");
            $thumbnailsContainer = $(".content-gallery-thumbnails-container");
            $thumbnails = $(".content-gallery-thumbnails-container > ul");

            $thumbnails.on("click", "li.choice", function () {
                setImageByIndex($(this).index());
            });

            $(".content-gallery-prev-image").click(previousImage)
            $(".content-gallery-next-image").click(nextImage);
            $scrollLeft.click(scrollLeft);
            $scrollRight.click(scrollRight);
        }

        function getSize(isSmall) {
            var size = gallery.getImageSize();
            thumbnailSize = gallery.getThumbnailSize();
            small = isSmall(size.width + 200, size.height + thumbnailSize.height + 45);
            imgSize = small ? gallery.getSmallImageSize() : gallery.getImageSize();
            thumbnailWidth = thumbnailSize.width + 8;
            resize();
            return {width: imgSize.width + 200, height: imgSize.height + thumbnailSize.height + 45};
        }

        function setData(data, callback) {
            $thumbnails.empty();

            gallery.load(data.app_label, data.content_type, data.object_id, function (response) {
                $.each(response, function (index, img) {
                    $thumbnails.append($("

")
                                .addClass("choice")
                                .addClass("content-gallery-centered-image")
                                .append($("")
                                    .attr("src", img.thumbnail)
                                    )
                                );
                });

                $choices = $thumbnails.children();
                callback();
            });
        }

        return {
            init: init,
            resize: resize,
            setData: setData,
            getSize: getSize
        };
    })(ContentGallery.gallery, ContentGallery.animateSync);

    ContentGallery.gallerySiteView = gallerySiteView;

    $(function () {
        gallerySiteView.init();
        ContentGallery.galleryView.init(gallerySiteView);
    });
})(jQuery);
<commit_msg>Fixed scroll thumbnails buttons<commit_after>(function ($) {

    window.ContentGallery = window.ContentGallery || {};

    var gallerySiteView = (function (gallery, animateSync) {
        var $imageContainer,
            $image,
            $thumbnails,
            $scrollLeft,
            $scrollRight,
            $choices,
            $thumbnailsView,
            $imageView,
            $thumbnailsContainer;

        var thumbnailWidth, maxOffset, imgSize, thumbnailSize, small;

        function checkScrollButtons(left) {
            if (left < 0)
                $scrollLeft.removeClass("content-gallery-inactive");
            else
                $scrollLeft.addClass("content-gallery-inactive");

            if (left > maxOffset)
                $scrollRight.removeClass("content-gallery-inactive");
            else
                $scrollRight.addClass("content-gallery-inactive");
        }

        function scrollToImage(index) {
            left = parseInt($thumbnails.css("left"));
            begin = thumbnailWidth * index;
            end = thumbnailWidth * (index + 1);

            if (left + begin >= 0 && left + end <= $thumbnailsContainer.width())
                return;

            if (left + begin < 0)
                newLeft = -begin;

            if (left + end > $thumbnailsContainer.width())
                newLeft = $thumbnailsContainer.width() - end;

            animateSync.safeAnimate($thumbnails, {left: newLeft}, null);
            checkScrollButtons(newLeft);
        }

        function setImage(index, callback) {
            img = gallery.getImage(index);
            if (!img) return;
            $choices.addClass("choice");
            $($choices[index]).removeClass("choice");
            if (small) {
                src = img.small_image;
                size = img.small_image_size;
            } else {
                src = img.image;
                size = img.image_size;
            }
            callback(size, src);
        }

        function setImageAnim(index) {
            setImage(index, function (size, src) {
                animateSync.safeAnimate($image, {width: 0, height: 0}, function () {
                    $image.attr("src", src);
                    animateSync.safeAnimate($image, {width: size.width, height: size.height}, null);
                });
            });
        }

        function setImageFast(index) {
            setImage(index, function (size, src) {
                $image.attr("src", src).css({width: size.width, height: size.height});
            });
        }

        function slideImage(index) {
            setImageAnim(index);
            scrollToImage(index);
        }

        function setThumbnailViewSize(imgSize, thumbnailSize) {
            $thumbnails.width(thumbnailWidth * gallery.count());

            container_width = Math.ceil(imgSize.width / thumbnailWidth) * thumbnailWidth;
            if (container_width > imgSize.width + 140)
                container_width -= thumbnailWidth;

            $thumbnailsView.width(container_width + 60);
            $choices.width(thumbnailSize.width)
                    .height(thumbnailSize.height)
                    .css("line-height", thumbnailSize.height + "px");
            $thumbnailsView.height(thumbnailSize.height + 2);
            $thumbnailsContainer.width(container_width);
        }

        function setImageHeight(height) {
            $imageView.height(height);
            $imageContainer.css({"line-height": height + "px"});
        }

        function nextImage() {
            animateSync.safeRun(function () {
                index = gallery.next();
                slideImage(index);
            });
        }

        function previousImage() {
            animateSync.safeRun(function () {
               index = gallery.prev();
               slideImage(index);
            });
        }

        function setImageByIndex(index) {
            animateSync.safeRun(function () {
                setImageAnim(index);
            });
        }

        function scrollLeft() {
            animateSync.safeRun(function () {
                left = parseInt($thumbnails.css("left"));
                if (left < 0) {
                    animateSync.safeAnimate($thumbnails, {left: "+=" + thumbnailWidth}, null);
                    checkScrollButtons(left + thumbnailWidth);
                }
            });
        }

        function scrollRight() {
            animateSync.safeRun(function () {
                left = parseInt($thumbnails.css("left"));
                if (left > maxOffset) {
                    animateSync.safeAnimate($thumbnails, {left: "-=" + thumbnailWidth}, null);
                    checkScrollButtons(left - thumbnailWidth);
                }
            });
        }

        function resize() {
            setThumbnailViewSize(imgSize, thumbnailSize);
            maxOffset = $thumbnailsContainer.width() - $thumbnails.width();

            setImageHeight(imgSize.height);

            index = gallery.current();
            setImageFast(index);

            left = parseInt($thumbnails.css("left"));
            checkScrollButtons(left);
        }

        function init() {
            $imageView = $("#content-gallery-image-view");
            $imageContainer = $(".content-gallery-image-container");
            $image = $(".content-gallery-image-container > img");
            $thumbnailsView = $("#content-gallery-thumbnails-view");
            $scrollLeft = $(".content-gallery-scroll-left");
            $scrollRight = $(".content-gallery-scroll-right");
            $thumbnailsContainer = $(".content-gallery-thumbnails-container");
            $thumbnails = $(".content-gallery-thumbnails-container > ul");

            $thumbnails.on("click", "li.choice", function () {
                setImageByIndex($(this).index());
            });

            $(".content-gallery-prev-image").click(previousImage)
            $(".content-gallery-next-image").click(nextImage);
            $scrollLeft.click(scrollLeft);
            $scrollRight.click(scrollRight);
        }

        function getSize(isSmall) {
            var size = gallery.getImageSize();
            thumbnailSize = gallery.getThumbnailSize();
            small = isSmall(size.width + 200, size.height + thumbnailSize.height + 45);
            imgSize = small ? gallery.getSmallImageSize() : gallery.getImageSize();
            thumbnailWidth = thumbnailSize.width + 8;
            resize();
            return {width: imgSize.width + 200, height: imgSize.height + thumbnailSize.height + 45};
        }

        function setData(data, callback) {
            $thumbnails.empty();
            $thumbnails.css("left", 0);

            gallery.load(data.app_label, data.content_type, data.object_id, function (response) {
                $.each(response, function (index, img) {
                    $thumbnails.append($("

")
                                .addClass("choice")
                                .addClass("content-gallery-centered-image")
                                .append($("")
                                    .attr("src", img.thumbnail)
                                    )
                                );
                });

                $choices = $thumbnails.children();
                callback();
            });
        }

        return {
            init: init,
            resize: resize,
            setData: setData,
            getSize: getSize
        };
    })(ContentGallery.gallery, ContentGallery.animateSync);

    ContentGallery.gallerySiteView = gallerySiteView;

    $(function () {
        gallerySiteView.init();
        ContentGallery.galleryView.init(gallerySiteView);
    });
})(jQuery);
<|endoftext|>