goog.provide('ol.test.Object');

describe('ol.Object', function() {

  var o;
  beforeEach(function() {
    o = new ol.Object();
  });

  describe('get and set', function() {

    describe('get an unset property', function() {
      var v;
      beforeEach(function() {
        v = o.get('k');
      });

      it('returns undefined', function() {
        expect(v).to.be(undefined);
      });

    });

    describe('get a set property', function() {
      var v;
      beforeEach(function() {
        o.set('k', 1);
        v = o.get('k');
      });

      it('returns expected value', function() {
        expect(v).to.eql(1);
      });
    });
  });

  describe('#get()', function() {

    it('does not return values that are not explicitly set', function() {
      var o = new ol.Object();
      expect(o.get('constructor')).to.be(undefined);
      expect(o.get('hasOwnProperty')).to.be(undefined);
      expect(o.get('isPrototypeOf')).to.be(undefined);
      expect(o.get('propertyIsEnumerable')).to.be(undefined);
      expect(o.get('toLocaleString')).to.be(undefined);
      expect(o.get('toString')).to.be(undefined);
      expect(o.get('valueOf')).to.be(undefined);
    });

  });

  describe('#set()', function() {
    it('can be used with arbitrary names', function() {
      var o = new ol.Object();

      o.set('set', 'sat');
      expect(o.get('set')).to.be('sat');

      o.set('get', 'got');
      expect(o.get('get')).to.be('got');

      o.set('toString', 'string');
      expect(o.get('toString')).to.be('string');
      expect(typeof o.toString).to.be('function');
    });
  });

  describe('#getKeys()', function() {

    it('returns property names set at construction', function() {
      var o = new ol.Object({
        prop1: 'val1',
        prop2: 'val2',
        toString: 'string',
        get: 'foo'
      });

      var keys = o.getKeys();
      expect(keys.length).to.be(4);
      expect(keys.sort()).to.eql(['get', 'prop1', 'prop2', 'toString']);
    });

  });

  describe('setValues', function() {

    it('sets multiple values at once', function() {
      o.setValues({
        k1: 1,
        k2: 2
      });
      expect(o.get('k1')).to.eql(1);
      expect(o.get('k2')).to.eql(2);

      var keys = o.getKeys().sort();
      expect(keys).to.eql(['k1', 'k2']);
    });
  });

  describe('notify', function() {

    var listener1, listener2, listener3;

    beforeEach(function() {
      listener1 = sinon.spy();
      goog.events.listen(o, 'change:k', listener1);

      listener2 = sinon.spy();
      goog.events.listen(o, 'change', listener2);

      var o2 = new ol.Object();
      o2.bindTo('k', o);
      listener3 = sinon.spy();
      goog.events.listen(o2, 'change:k', listener3);
    });

    it('dispatches events', function() {
      o.notify('k');
      expect(listener1).to.be.called();
    });

    it('dispatches generic change events to bound objects', function() {
      o.notify('k');
      expect(listener2).to.be.called();
    });

    it('dispatches events to bound objects', function() {
      o.notify('k');
      expect(listener3).to.be.called();
    });
  });

  describe('set', function() {

    var listener1, o2, listener2, listener3;

    beforeEach(function() {
      listener1 = sinon.spy();
      goog.events.listen(o, 'change:k', listener1);

      listener2 = sinon.spy();
      goog.events.listen(o, 'change', listener2);

      o2 = new ol.Object();
      o2.bindTo('k', o);
      listener3 = sinon.spy();
      goog.events.listen(o2, 'change:k', listener3);
    });

    it('dispatches events to object', function() {
      o.set('k', 1);
      expect(listener1).to.be.called();

      expect(o.getKeys()).to.eql(['k']);
      expect(o2.getKeys()).to.eql(['k']);
    });

    it('dispatches generic change events to object', function() {
      o.set('k', 1);
      expect(listener2).to.be.called();
    });

    it('dispatches events to bound object', function() {
      o.set('k', 1);
      expect(listener3).to.be.called();
    });

    it('dispatches events to object bound to', function() {
      o2.set('k', 2);
      expect(listener1).to.be.called();

      expect(o.getKeys()).to.eql(['k']);
      expect(o2.getKeys()).to.eql(['k']);
    });

    it('dispatches generic change events to object bound to', function() {
      o2.set('k', 2);
      expect(listener2).to.be.called();
    });
  });

  describe('bind', function() {

    var o2;

    beforeEach(function() {
      o2 = new ol.Object();
    });

    describe('bindTo after set', function() {

      it('gets expected value', function() {
        o.set('k', 1);
        o2.bindTo('k', o);
        expect(o.get('k')).to.eql(1);
        expect(o2.get('k')).to.eql(1);

        expect(o.getKeys()).to.eql(['k']);
        expect(o2.getKeys()).to.eql(['k']);
      });
    });

    describe('bindTo before set', function() {

      it('gets expected value', function() {
        o2.bindTo('k', o);
        o.set('k', 1);
        expect(o.get('k')).to.eql(1);
        expect(o2.get('k')).to.eql(1);

        expect(o.getKeys()).to.eql(['k']);
        expect(o2.getKeys()).to.eql(['k']);
      });
    });

    describe('backwards', function() {
      describe('bindTo after set', function() {

        it('gets expected value', function() {
          o2.set('k', 1);
          o2.bindTo('k', o);
          expect(o.get('k')).to.be(undefined);
          expect(o2.get('k')).to.be(undefined);
        });
      });

      describe('bindTo before set', function() {

        it('gets expected value', function() {
          o2.bindTo('k', o);
          o2.set('k', 1);
          expect(o.get('k')).to.eql(1);
          expect(o2.get('k')).to.eql(1);
        });
      });
    });
  });

  describe('unbind', function() {
    var o2;

    beforeEach(function() {
      o2 = new ol.Object();
      o2.bindTo('k', o);
      o2.set('k', 1);
    });

    it('makes changes to unbound object invisible to other object', function() {
      // initial state
      expect(o.get('k')).to.eql(1);
      expect(o2.get('k')).to.eql(1);
      o2.unbind('k');
      expect(o.get('k')).to.eql(1);
      expect(o2.get('k')).to.eql(1);
      o2.set('k', 2);
      expect(o.get('k')).to.eql(1);
      expect(o2.get('k')).to.eql(2);
    });
  });

  describe('unbindAll', function() {
    var o2;

    beforeEach(function() {
      o2 = new ol.Object();
      o2.bindTo('k', o);
      o2.set('k', 1);
    });

    it('makes changes to unbound object invisible to other object', function() {
      // initial state
      expect(o.get('k')).to.eql(1);
      expect(o2.get('k')).to.eql(1);
      o2.unbindAll();
      expect(o.get('k')).to.eql(1);
      expect(o2.get('k')).to.eql(1);
      o2.set('k', 2);
      expect(o.get('k')).to.eql(1);
      expect(o2.get('k')).to.eql(2);
    });
  });

  describe('bind rename', function() {
    var listener1, o2, listener2;

    beforeEach(function() {
      o2 = new ol.Object();
      o2.bindTo('k2', o, 'k1');

      listener1 = sinon.spy();
      goog.events.listen(o, 'change:k1', listener1);

      listener2 = sinon.spy();
      goog.events.listen(o2, 'change:k2', listener2);
    });

    it('sets the expected properties', function() {
      o.set('k1', 1);
      expect(o.get('k1')).to.eql(1);
      expect(o.get('k2')).to.be(undefined);
      expect(o2.get('k2')).to.eql(1);
      expect(o2.get('k1')).to.be(undefined);
      expect(listener1).to.be.called();
      expect(listener2).to.be.called();

      expect(o.getKeys()).to.eql(['k1']);
      expect(o2.getKeys()).to.eql(['k2']);
    });
  });

  describe('transitive bind', function() {
    var o2, o3;

    beforeEach(function() {
      o2 = new ol.Object();
      o3 = new ol.Object();
      o2.bindTo('k2', o, 'k1');
      o3.bindTo('k3', o2, 'k2');
    });

    it('sets the expected properties', function() {
      o.set('k1', 1);
      expect(o.get('k1')).to.eql(1);
      expect(o2.get('k2')).to.eql(1);
      expect(o3.get('k3')).to.eql(1);

      expect(o.getKeys()).to.eql(['k1']);
      expect(o2.getKeys()).to.eql(['k2']);
      expect(o3.getKeys()).to.eql(['k3']);
    });

    describe('backward', function() {

      it('sets the expected properties', function() {
        o3.set('k3', 1);
        expect(o.get('k1')).to.eql(1);
        expect(o2.get('k2')).to.eql(1);
        expect(o3.get('k3')).to.eql(1);

        expect(o.getKeys()).to.eql(['k1']);
        expect(o2.getKeys()).to.eql(['k2']);
        expect(o3.getKeys()).to.eql(['k3']);
      });
    });
  });

  describe('circular bind', function() {
    var o2;

    beforeEach(function() {
      o2 = new ol.Object();
      o.bindTo('k', o2);
    });

    it('throws an error', function() {
      expect(function() { o2.bindTo('k', o); }).to.throwException();
    });
  });

  describe('priority', function() {
    var o2;

    beforeEach(function() {
      o2 = new ol.Object();
    });

    it('respects set order', function() {
      o.set('k', 1);
      o2.set('k', 2);
      o.bindTo('k', o2);
      expect(o.get('k')).to.eql(2);
      expect(o2.get('k')).to.eql(2);
    });

    it('respects set order (undefined)', function() {
      o.set('k', 1);
      o.bindTo('k', o2);
      expect(o.get('k')).to.be(undefined);
      expect(o2.get('k')).to.be(undefined);
    });
  });

  describe('setter', function() {
    beforeEach(function() {
      o.setX = function(x) {
        this.set('x', x);
      };
      sinon.spy(o, 'setX');
    });

    describe('without bind', function() {
      it('does not call the setter', function() {
        o.set('x', 1);
        expect(o.get('x')).to.eql(1);
        expect(o.setX).to.not.be.called();

        expect(o.getKeys()).to.eql(['x']);
      });
    });

    describe('with bind', function() {
      it('does call the setter', function() {
        var o2 = new ol.Object();
        o2.bindTo('x', o);
        o2.set('x', 1);
        expect(o.setX).to.be.called();
        expect(o.get('x')).to.eql(1);

        expect(o.getKeys()).to.eql(['x']);
        expect(o2.getKeys()).to.eql(['x']);
      });
    });
  });

  describe('getter', function() {
    beforeEach(function() {
      o.getX = function() {
        return 1;
      };
      sinon.spy(o, 'getX');
    });

    describe('without bind', function() {
      it('does not call the getter', function() {
        expect(o.get('x')).to.be(undefined);
        expect(o.getX).to.not.be.called();
      });
    });

    describe('with bind', function() {
      it('does call the getter', function() {
        var o2 = new ol.Object();
        o2.bindTo('x', o);
        expect(o2.get('x')).to.eql(1);
        expect(o.getX).to.be.called();

        expect(o.getKeys()).to.eql([]);
        expect(o2.getKeys()).to.eql(['x']);
      });
    });
  });

  describe('bind self', function() {
    it('throws an error', function() {
      expect(function() { o.bindTo('k', o); }).to.throwException();
    });
  });

  describe('create with options', function() {
    it('sets the property', function() {
      var o = new ol.Object({k: 1});
      expect(o.get('k')).to.eql(1);

      expect(o.getKeys()).to.eql(['k']);
    });
  });

  describe('case sensitivity', function() {
    var listener1, listener2;

    beforeEach(function() {
      listener1 = sinon.spy();
      goog.events.listen(o, 'change:k', listener1);
      listener2 = sinon.spy();
      goog.events.listen(o, 'change:K', listener2);
    });

    it('dispatches the expected event', function() {
      o.set('K', 1);
      expect(listener1).to.be.called();
      expect(listener2).to.not.be.called();

      expect(o.getKeys()).to.eql(['K']);
    });
  });
});


goog.require('goog.events');
goog.require('ol.Object');
