<commit_before>'use strict';

var nodeCache = require('../../lib/cache-lib/node-cache');
var MultiCache = require('../..');
var assert = require('assert');
var _ = require('lodash');
var sinon = require('sinon');

describe('Multi Cache',function(){
  var localCacheName = 'node-cache';
  var remoteCacheName = 'node-cache';
  var testRemoteOnly = {
    useLocalCache: false,
    useRemoteCache: true
  };
  var testLocalOnly = {
    useLocalCache: true,
    useRemoteCache: false
  };
  var testBothActive = {
    useLocalCache: true,
    useRemoteCache: true
  };
  var testBothInactive = {
    useLocalCache: false,
    useRemoteCache: false
  };

  describe('Class creation',function() {

    it('should create a Multi-Cache without options', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      assert(multiCache.useLocalCacheDefault);
      assert(multiCache.useRemoteCacheDefault);
      // TODO: Add sinon to confirm that the createCache function is called.
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.get('myKey', function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Test that key/value is in remoteCache as well because if
          // we create the Multi Cache without options then both remote
          // and local are switched on by default.
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert.equal(value, 'myValue');
            done();
          });
        });
      });
    });

    it('should create a Multi-Cache with pre-created caches', function (done) {
      // Pass in pre-created cache objects to create a Multi-Cache instead of
      // names for the cache objects.
      var localCache = nodeCache();
      var remoteCache = nodeCache();

      var multiCache = new MultiCache(localCache, remoteCache, testLocalOnly);
      // TODO: Add sinon to confirm that the createCache function is NOT called.
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          multiCache.get('myKey', testRemoteOnly, function(err, value){
            assert(!err);
            assert(_.isEmpty(value));
            done();
          });
        });
      });
    });

  });

  describe('Setting',function() {

    it('should set an object in the local cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testLocalOnly);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Test that key/value is not in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should set an object in the remote cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testRemoteOnly);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert(_.isEmpty(value));
          // Test that key/value is in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should set an object in both remote and local caches', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert(!_.isEmpty(value));
          // Test that key/value is in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should set with two params on set()', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      multiCache.set('myKey', 'myValue');
      // .set() is async so wait for 500ms before testing that the value
      // has been set. We're doing this test to check the "else" branch
      // in the target code.
      setTimeout(function() {
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert(!_.isEmpty(value));
          assert.equal(value, 'myValue');
          // Test that key/value is in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            assert.equal(value, 'myValue');
            done();
          });
        });
      }, 500);
    });

    it('should throw with no callback and no caches on set()', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothInactive);
      try {
        multiCache.set('myKey', 'myValue');
      } catch(e) {
        assert.equal('local or remote must be specified when setting to cache', e.message);
        done();
      }
    });

    it('should return an error for neither caches during set', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothInactive);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(err);
        assert(_.isEmpty(result));
        assert.equal('local or remote must be specified when setting to cache', err.message);
        done();
      });
    });

    it('should return an error for neither caches during get', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testBothInactive, function (err, value) {
          assert(typeof err === 'object');
          assert(_.isEmpty(value));
          assert.equal('local or remote must be specified when getting from cache', err.message);
          done();
        });
      });
    });

  });

  describe('Getting',function() {

    it('should get an object from the remote cache if local is empty', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      multiCache.set('myKey', 'myValue', testRemoteOnly, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Confirm that key is not in local cache
          multiCache.get('myKey', testLocalOnly, function (err, value) {
            assert(!err);
            assert(_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should set an object in local cache if setLocal is true', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      multiCache.set('myKey', 'myValue', testRemoteOnly, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', {setLocal: true}, function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Confirm that key is now also in local cache
          multiCache.get('myKey', testLocalOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should handle the local cache returning an error on get', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      var localStub = sinon.stub(multiCache.localCache, 'get', function(keys, callback){
        return callback('fake error', 'fake value');
      });
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert.equal('fake error', err);
          assert.equal('fake value', value);
          localStub.restore();
          done();
        });
      });
    });

    it('should handle the remote cache returning an error on get', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      var remoteStub = sinon.stub(multiCache.remoteCache, 'get', function(keys, callback){
        return callback('fake error', 'fake value');
      });
      multiCache.set('myKey', 'myValue', testRemoteOnly, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert.equal('fake error', err);
          assert.equal('fake value', value);
          remoteStub.restore();
          done();
        });
      });
    });

  });

  describe('Deleting',function() {

    it('should delete an object in the local cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testLocalOnly, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testLocalOnly, function (err, value) {
            assert(!err);
            assert(_.isEmpty(value));
            multiCache.get('myKey', testRemoteOnly, function (err, value) {
              assert(!err);
              assert(!_.isEmpty(value));
              done();
            });
          });
        });
      });
    });

    it('should delete an object in the remote cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testRemoteOnly, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(_.isEmpty(value));
            multiCache.get('myKey', testLocalOnly, function (err, value) {
              assert(!err);
              assert(!_.isEmpty(value));
              done();
            });
          });
        });
      });
    });

    it('should delete an object in both remote and local caches', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testBothActive, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(_.isEmpty(value));
            multiCache.get('myKey', testLocalOnly, function (err, value) {
              assert(!err);
              assert(_.isEmpty(value));
              done();
            });
          });
        });
      });
    });

    it('should not delete an object in either remote and local caches', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testBothInactive, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            multiCache.get('myKey', testLocalOnly, function (err, value) {
              assert(!err);
              assert(!_.isEmpty(value));
              done();
            });
          });
        });
      });
    });
  });

  describe('Complex objects',function() {

    it('should set and get complex objects', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      var value = {
        nested: {
          obj: {
            which: {
              keeps: {
                getting: {
                  deeper: {
                    and: {
                      deeper: {
                        and: {
                          has: {
                            an: {
                              array: {
                                inside: {
                                  it: [
                                    1,
                                    1,
                                    2,
                                    6,
                                    24,
                                    {an: 'object'},
                                    'a string',
                                    new Date(),
                                    true,
                                    false
                                  ],
                                  and: {
                                    a: {
                                      date: new Date()
                                    }
                                  },
                                  a: {
                                    number: 1234
                                  },
                                  bool: true,
                                  string: 'another string'
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      };
      // Test that the cache returns the same complex object as what was set
      multiCache.set('myKey', value, testBothActive, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        // Confirm value from local cache
        multiCache.get('myKey', testLocalOnly, function (err, result) {
          assert(!err);
          assert.deepEqual(result, value);
          // Confirm value from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, result) {
            assert(!err);
            assert.deepEqual(result, value);
            done();
          });
        });
      });
    });
  });

});
<commit_msg>Check for undefined if key does not exist<commit_after>'use strict';

var nodeCache = require('../../lib/cache-lib/node-cache');
var MultiCache = require('../..');
var assert = require('assert');
var _ = require('lodash');
var sinon = require('sinon');

describe('Multi Cache',function(){
  var localCacheName = 'node-cache';
  var remoteCacheName = 'node-cache';
  var testRemoteOnly = {
    useLocalCache: false,
    useRemoteCache: true
  };
  var testLocalOnly = {
    useLocalCache: true,
    useRemoteCache: false
  };
  var testBothActive = {
    useLocalCache: true,
    useRemoteCache: true
  };
  var testBothInactive = {
    useLocalCache: false,
    useRemoteCache: false
  };

  describe('Class creation',function() {

    it('should create a Multi-Cache without options', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      assert(multiCache.useLocalCacheDefault);
      assert(multiCache.useRemoteCacheDefault);
      // TODO: Add sinon to confirm that the createCache function is called.
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.get('myKey', function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Test that key/value is in remoteCache as well because if
          // we create the Multi Cache without options then both remote
          // and local are switched on by default.
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert.equal(value, 'myValue');
            done();
          });
        });
      });
    });

    it('should create a Multi-Cache with pre-created caches', function (done) {
      // Pass in pre-created cache objects to create a Multi-Cache instead of
      // names for the cache objects.
      var localCache = nodeCache();
      var remoteCache = nodeCache();

      var multiCache = new MultiCache(localCache, remoteCache, testLocalOnly);
      // TODO: Add sinon to confirm that the createCache function is NOT called.
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          multiCache.get('myKey', testRemoteOnly, function(err, value){
            assert(!err);
            assert.equal(undefined, value);
            done();
          });
        });
      });
    });

  });

  describe('Setting',function() {

    it('should set an object in the local cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testLocalOnly);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Test that key/value is not in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert.equal(undefined, value);
            done();
          });
        });
      });
    });

    it('should set an object in the remote cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testRemoteOnly);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert.equal(undefined, value);
          // Test that key/value is in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should set an object in both remote and local caches', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert(!_.isEmpty(value));
          // Test that key/value is in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should set with two params on set()', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      multiCache.set('myKey', 'myValue');
      // .set() is async so wait for 500ms before testing that the value
      // has been set. We're doing this test to check the "else" branch
      // in the target code.
      setTimeout(function() {
        multiCache.get('myKey', testLocalOnly, function (err, value) {
          assert(!err);
          assert(!_.isEmpty(value));
          assert.equal(value, 'myValue');
          // Test that key/value is in remoteCache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            assert.equal(value, 'myValue');
            done();
          });
        });
      }, 500);
    });

    it('should throw with no callback and no caches on set()', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothInactive);
      try {
        multiCache.set('myKey', 'myValue');
      } catch(e) {
        assert.equal('local or remote must be specified when setting to cache', e.message);
        done();
      }
    });

    it('should return an error for neither caches during set', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothInactive);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(err);
        assert(result === undefined);
        assert.equal('local or remote must be specified when setting to cache', err.message);
        done();
      });
    });

    it('should return an error for neither caches during get', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      assert.notEqual(multiCache.localCache, multiCache.remoteCache);
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', testBothInactive, function (err, value) {
          assert(typeof err === 'object');
          assert.equal(undefined, value);
          assert.equal('local or remote must be specified when getting from cache', err.message);
          done();
        });
      });
    });

  });

  describe('Getting',function() {

    it('should get an object from the remote cache if local is empty', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      multiCache.set('myKey', 'myValue', testRemoteOnly, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Confirm that key is not in local cache
          multiCache.get('myKey', testLocalOnly, function (err, value) {
            assert(!err);
            assert.equal(undefined, value);
            done();
          });
        });
      });
    });

    it('should set an object in local cache if setLocal is true', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      multiCache.set('myKey', 'myValue', testRemoteOnly, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', {setLocal: true}, function (err, value) {
          assert(!err);
          assert.equal(value, 'myValue');
          // Confirm that key is now also in local cache
          multiCache.get('myKey', testLocalOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            done();
          });
        });
      });
    });

    it('should handle the local cache returning an error on get', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      var localStub = sinon.stub(multiCache.localCache, 'get', function(keys, callback){
        return callback('fake error', 'fake value');
      });
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert.equal('fake error', err);
          assert.equal('fake value', value);
          localStub.restore();
          done();
        });
      });
    });

    it('should handle the remote cache returning an error on get', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName, testBothActive);
      var remoteStub = sinon.stub(multiCache.remoteCache, 'get', function(keys, callback){
        return callback('fake error', 'fake value');
      });
      multiCache.set('myKey', 'myValue', testRemoteOnly, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        multiCache.get('myKey', function (err, value) {
          assert.equal('fake error', err);
          assert.equal('fake value', value);
          remoteStub.restore();
          done();
        });
      });
    });

  });

  describe('Deleting',function() {

    it('should delete an object in the local cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testLocalOnly, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testLocalOnly, function (err, value) {
            assert(!err);
            assert.equal(undefined, value);
            multiCache.get('myKey', testRemoteOnly, function (err, value) {
              assert(!err);
              assert.equal('myValue', value);
              done();
            });
          });
        });
      });
    });

    it('should delete an object in the remote cache only', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testRemoteOnly, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert.equal(undefined, value);
            multiCache.get('myKey', testLocalOnly, function (err, value) {
              assert(!err);
              assert(!_.isEmpty(value));
              done();
            });
          });
        });
      });
    });

    it('should delete an object in both remote and local caches', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testBothActive, function (err) {
          assert(!err);
          // Check that key has been deleted from both caches
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert.equal(undefined, value);
            multiCache.get('myKey', testLocalOnly, function (err, value) {
              assert(!err);
              assert.equal(undefined, value);
              done();
            });
          });
        });
      });
    });

    it('should not delete an object in either remote and local caches', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      // Set a key/value in both local and remote caches
      // Set remoteCache to true to override the default from above
      multiCache.set('myKey', 'myValue', function (err, result) {
        assert(!err);
        assert(result);
        multiCache.del('myKey', testBothInactive, function (err) {
          assert(!err);
          // Check that key has been deleted from local cache but not
          // from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, value) {
            assert(!err);
            assert(!_.isEmpty(value));
            multiCache.get('myKey', testLocalOnly, function (err, value) {
              assert(!err);
              assert(!_.isEmpty(value));
              done();
            });
          });
        });
      });
    });
  });

  describe('Complex objects',function() {

    it('should set and get complex objects', function (done) {
      var multiCache = new MultiCache(localCacheName, remoteCacheName);
      var value = {
        nested: {
          obj: {
            which: {
              keeps: {
                getting: {
                  deeper: {
                    and: {
                      deeper: {
                        and: {
                          has: {
                            an: {
                              array: {
                                inside: {
                                  it: [
                                    1,
                                    1,
                                    2,
                                    6,
                                    24,
                                    {an: 'object'},
                                    'a string',
                                    new Date(),
                                    true,
                                    false
                                  ],
                                  and: {
                                    a: {
                                      date: new Date()
                                    }
                                  },
                                  a: {
                                    number: 1234
                                  },
                                  bool: true,
                                  string: 'another string'
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      };
      // Test that the cache returns the same complex object as what was set
      multiCache.set('myKey', value, testBothActive, function (err, result) {
        assert(!err);
        assert(!_.isEmpty(result));
        // Confirm value from local cache
        multiCache.get('myKey', testLocalOnly, function (err, result) {
          assert(!err);
          assert.deepEqual(result, value);
          // Confirm value from remote cache
          multiCache.get('myKey', testRemoteOnly, function (err, result) {
            assert(!err);
            assert.deepEqual(result, value);
            done();
          });
        });
      });
    });
  });

});
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React, { Component } from 'react'
import autobind from 'autobind-decorator'
import Autocomplete from 'components/Autocomplete'
import {Modal, Button, Grid, Row, Col, Alert, Table} from 'react-bootstrap'
import {Position, Person} from 'models'
import LinkTo from 'components/LinkTo'
import Messages from 'components/Messages'
import API from 'api'
import _isEmpty from 'lodash/isEmpty'
import AppContext from 'components/AppContext'

class BaseAssignPositionModal extends Component {
	static propTypes = {
		person: PropTypes.object.isRequired,
		showModal: PropTypes.bool,
		onCancel: PropTypes.func.isRequired,
		onSuccess: PropTypes.func.isRequired,
		currentUser: PropTypes.instanceOf(Person),
	}

	constructor(props) {
		super(props)
		this.state = {
			error: null,
			position: props.person && props.person.position
		}
	}

	componentDidUpdate(prevProps, prevState) {
		if (prevProps.person.position !== this.props.person.position) {
			this.setState({position: this.props.person.position}, () => this.updateAlert())
		}
	}

	render() {
		const { person, currentUser } = this.props
		let newPosition = new Position(this.state.position)

		let positionSearchQuery = {status: Position.STATUS.ACTIVE}
		if (person.role === Person.ROLE.ADVISOR) {
			positionSearchQuery.type = [Position.TYPE.ADVISOR]
			if (currentUser.isAdmin()) { //only admins can put people in admin billets.
				positionSearchQuery.type.push(Position.TYPE.ADMINISTRATOR)
				positionSearchQuery.type.push(Position.TYPE.SUPER_USER)
			} else if (currentUser.isSuperUser()) {
				//Only super users can put people in super user billets
				//And they are limited to their organization.
				positionSearchQuery.type.push(Position.TYPE.SUPER_USER)
				positionSearchQuery.organizationUuid = currentUser.position.organization.uuid
				positionSearchQuery.includeChildrenOrgs = true
			}
		} else if (person.role === Person.ROLE.PRINCIPAL) {
			positionSearchQuery.type = [Position.TYPE.PRINCIPAL]
		}

		return (
			<Modal show={this.props.showModal} onHide={this.close}>
				<Modal.Header closeButton>
					<Modal.Title>Set Position for <LinkTo person={person} isLink={false}/></Modal.Title>
				</Modal.Header>
				<Modal.Body>
					{person.position.uuid &&

							<Button bsStyle="danger" onClick={this.remove} className="remove-person-from-position">
								Remove <LinkTo person={person} isLink={false}/> from <LinkTo position={person.position} isLink={false}/>
							</Button>

					}
					<Grid fluid>
						<Row>
							<Col md={2}>
								Select a position
							</Col>
							<Col md={10}>
								<Autocomplete valueKey="name"
									placeholder="Select a position for this person"
									objectType={Position}
									fields={'uuid, name, code, type, organization { uuid, shortName, longName, identificationCode}, person { uuid, name, rank, role }'}
									template={pos =>
										{[pos.name, pos.code].join(' - ')}
									}
									queryParams={positionSearchQuery}
									value={this.state.position}
									onChange={this.onPositionSelect}
								/>
							</Col>
						</Row>
						{newPosition && newPosition.uuid &&

										Organization
										Type
										Current Person

											{newPosition.organization.shortName}

											{newPosition.humanNameOfType()}

											{newPosition.person ?
												newPosition.person.name
												:
												(newPosition.uuid === person.position.uuid ?
													person.name
													:
													Unfilled
												)
											}

						}
						<Messages error={this.state.error} />
					</Grid>
				</Modal.Body>
				<Modal.Footer>
					<Button className="pull-left" onClick={this.close}>Cancel</Button>
					<Button onClick={this.save} bsStyle={"primary"} >Save</Button>
				</Modal.Footer>
			</Modal>
		)
	}

	@autobind
	remove() {
		let graphql = 'deletePersonFromPosition(uuid: $uuid)'
		const variables = {
			uuid: this.props.person.position.uuid,
		}
		const variableDef = '($uuid: String!)'
		API.mutation(graphql, variables, variableDef)
			.then(
				data => this.props.onSuccess()
			).catch(error => {
				this.setState({error: error})
			})
	}

	@autobind
	save() {
		const operation = 'putPersonInPosition'
		let graphql = operation + '(uuid: $uuid, person: $person)'
		const variables = {
			uuid: this.state.position.uuid,
			person: {uuid: this.props.person.uuid}
		}
		const variableDef = '($uuid: String!, $person: PersonInput!)'
		API.mutation(graphql, variables, variableDef)
			.then(
				data => this.props.onSuccess()
			).catch(error => {
				this.setState({error: error})
			})
	}

	@autobind
	close() {
		// Reset state before closing (cancel)
		this.setState({position: this.props.person.position}, () => this.updateAlert())
		this.props.onCancel()
	}

	@autobind
	onPositionSelect(position) {
		if (position.uuid) {
			this.setState({position}, () => this.updateAlert())
		}
	}

	@autobind
	updateAlert() {
		let error = null
		if (!_isEmpty(this.state.position) && !_isEmpty(this.state.position.person) && this.state.position.person.uuid !== this.props.person.uuid){
			const errorMessage = <React.Fragment>This position is currently held by <LinkTo person={this.state.position.person}/>.  By selecting this position, they will be removed.</React.Fragment>
			error = {message: errorMessage}
		}
		this.setState({error: error})
	}

}
const AssignPositionModal = (props) => (
	<AppContext.Consumer>
		{context =>
			<BaseAssignPositionModal currentUser={context.currentUser} {...props} />
		}
	</AppContext.Consumer>
)

export default AssignPositionModal
<commit_msg>NCI-Agency/anet#1326: Use AdvancedSingleSelect in assign position modal<commit_after>import PropTypes from 'prop-types'
import React, { Component } from 'react'
import autobind from 'autobind-decorator'

import AdvancedSingleSelect from 'components/advancedSelectWidget/AdvancedSingleSelect'
import {Modal, Button, Grid, Row, Col, Alert, Table} from 'react-bootstrap'
import {Position, Person} from 'models'
import LinkTo from 'components/LinkTo'
import Messages from 'components/Messages'
import API from 'api'
import _isEmpty from 'lodash/isEmpty'
import AppContext from 'components/AppContext'

import POSITIONS_ICON from 'resources/positions.png'

class BaseAssignPositionModal extends Component {
	static propTypes = {
		person: PropTypes.object.isRequired,
		showModal: PropTypes.bool,
		onCancel: PropTypes.func.isRequired,
		onSuccess: PropTypes.func.isRequired,
		currentUser: PropTypes.instanceOf(Person),
	}

	constructor(props) {
		super(props)
		this.state = {
			error: null,
			position: props.person && props.person.position
		}
	}

	componentDidUpdate(prevProps, prevState) {
		if (prevProps.person.position !== this.props.person.position) {
			this.setState({position: this.props.person.position}, () => this.updateAlert())
		}
	}

	render() {
		const { person, currentUser } = this.props
		let newPosition = new Position(this.state.position)

		let positionSearchQuery = {status: Position.STATUS.ACTIVE}
		if (person.role === Person.ROLE.ADVISOR) {
			positionSearchQuery.type = [Position.TYPE.ADVISOR]
			if (currentUser.isAdmin()) { //only admins can put people in admin billets.
				positionSearchQuery.type.push(Position.TYPE.ADMINISTRATOR)
				positionSearchQuery.type.push(Position.TYPE.SUPER_USER)
			} else if (currentUser.isSuperUser()) {
				//Only super users can put people in super user billets
				//And they are limited to their organization.
				positionSearchQuery.type.push(Position.TYPE.SUPER_USER)
				positionSearchQuery.organizationUuid = currentUser.position.organization.uuid
				positionSearchQuery.includeChildrenOrgs = true
			}
		} else if (person.role === Person.ROLE.PRINCIPAL) {
			positionSearchQuery.type = [Position.TYPE.PRINCIPAL]
		}
		const positionsFilters = {
			allAdvisorPositions: {
				label: 'All',
				searchQuery: true,
				queryVars: positionSearchQuery,
			}
		}

		return (
			<Modal show={this.props.showModal} onHide={this.close}>
				<Modal.Header closeButton>
					<Modal.Title>Set Position for <LinkTo person={person} isLink={false}/></Modal.Title>
				</Modal.Header>
				<Modal.Body>
					{person.position.uuid &&

							<Button bsStyle="danger" onClick={this.remove} className="remove-person-from-position">
								Remove <LinkTo person={person} isLink={false}/> from <LinkTo position={person.position} isLink={false}/>
							</Button>

					}
					<Grid fluid>
						<Row>
							<Col md={10}>
								<AdvancedSingleSelect
									fieldName="position"
									fieldLabel="Select a position"
									placeholder="Select a position for this person"
									value={this.state.position}
									overlayColumns={['', 'Name', 'Position']}
									overlayRenderRow={this.renderPositionOverlayRow}
									filterDefs={positionsFilters}
									onChange={this.onPositionSelect}
									objectType={Position}
									valueKey="name"
									fields={'uuid, name, code, type, organization { uuid, shortName, longName, identificationCode}, person { uuid, name, rank, role }'}
									addon={POSITIONS_ICON}
									vertical={true}
								/>
							</Col>
						</Row>
						{newPosition && newPosition.uuid &&

										Organization
										Type
										Current Person

											{newPosition.organization.shortName}

											{newPosition.humanNameOfType()}

											{newPosition.person ?
												newPosition.person.name
												:
												(newPosition.uuid === person.position.uuid ?
													person.name
													:
													Unfilled
												)
											}

						}
						<Messages error={this.state.error} />
					</Grid>
				</Modal.Body>
				<Modal.Footer>
					<Button className="pull-left" onClick={this.close}>Cancel</Button>
					<Button onClick={this.save} bsStyle={"primary"} >Save</Button>
				</Modal.Footer>
			</Modal>
		)
	}

	@autobind
	remove() {
		let graphql = 'deletePersonFromPosition(uuid: $uuid)'
		const variables = {
			uuid: this.props.person.position.uuid,
		}
		const variableDef = '($uuid: String!)'
		API.mutation(graphql, variables, variableDef)
			.then(
				data => this.props.onSuccess()
			).catch(error => {
				this.setState({error: error})
			})
	}

	@autobind
	save() {
		let graphql = 'putPersonInPosition(uuid: $uuid, person: $person)'
		let variables = {
			uuid: this.state.position.uuid,
			person: {uuid: this.props.person.uuid}
		}
		let variableDef = '($uuid: String!, $person: PersonInput!)'
		if (this.state.position === null) {
			graphql = 'deletePersonFromPosition(uuid: $uuid)'
			variables = {
				uuid: this.props.person.position.uuid,
			}
			variableDef = '($uuid: String!)'
		}

		API.mutation(graphql, variables, variableDef)
		.then(
			data => this.props.onSuccess()
		).catch(error => {
			this.setState({error: error})
		})
	}

	@autobind
	close() {
		// Reset state before closing (cancel)
		this.setState({position: this.props.person.position}, () => this.updateAlert())
		this.props.onCancel()
	}

	@autobind
	onPositionSelect(position) {
		if (position && position.uuid) {
			this.setState({position}, () => this.updateAlert())
		}
	}

	@autobind
	updateAlert() {
		let error = null
		if (!_isEmpty(this.state.position) && !_isEmpty(this.state.position.person) && this.state.position.person.uuid !== this.props.person.uuid){
			const errorMessage = <React.Fragment>This position is currently held by <LinkTo person={this.state.position.person}/>.  By selecting this position, they will be removed.</React.Fragment>
			error = {message: errorMessage}
		}
		this.setState({error: error})
	}

	renderPositionOverlayRow = (item) => {
		return (
			<React.Fragment key={item.uuid}>
				<LinkTo person={item.person} isLink={false} />
				<LinkTo position={item} isLink={false} />
			</React.Fragment>
		)
	}

}
const AssignPositionModal = (props) => (
	<AppContext.Consumer>
		{context =>
			<BaseAssignPositionModal currentUser={context.currentUser} {...props} />
		}
	</AppContext.Consumer>
)

export default AssignPositionModal
<|endoftext|>


<commit_before>'use strict';

angular.module('camManager')
    .directive('camera', ['CameraFactory', function(CameraFactory) {
        return {
            templateUrl: './templates/camera-detail.html',
            scope: {
                id: '=cameraId',
                cameraData: '='
            }
        }
    }]);
<commit_msg>Rig orientation param in camera directive<commit_after>'use strict';

angular.module('camManager')
    .directive('camera', ['CameraFactory', function(CameraFactory) {
        return {
            templateUrl: './templates/camera-detail.html',
            scope: {
                id: '=cameraId',
                cameraData: '=',
                rigOrientation: '='
            }
        }
    }]);
<|endoftext|>


<commit_before>import { moduleForModel, test } from 'ember-qunit';
import set from 'ember-metal/set';
import get from 'ember-metal/get';
import run from 'ember-runloop';

moduleForModel('anime', 'Unit | Model | anime', {
  needs: ['model:genre']
});

test('typeStr', function(assert) {
  assert.expect(6);
  const anime = this.subject();

  // 1 - TV, 2 - Special, 3 - ONA, 4 - OVA, 5 - Movie, 6 - Music
  const data = ['TV', 'Special', 'ONA', 'OVA', 'Movie', 'Music'];
  run(() => {
    for (let i = 0; i < data.length; ++i) {
      set(anime, 'showType', i + 1);
      assert.equal(get(anime, 'typeStr'), data[i]);
    }
  });
});
<commit_msg>client: Remove anime test for removed computed property<commit_after><|endoftext|>


<commit_before>import React from 'react';
import { injectIntl } from 'react-intl';
import Link from 'react-router/lib/Link';
import { Col } from 'react-flexbox-grid/lib';
import Lock from 'material-ui/svg-icons/action/lock';
import IconButton from 'material-ui/IconButton';
import DataCard from '../../common/DataCard';
import FavoriteToggler from '../../common/FavoriteToggler';
import { PERMISSION_TOPIC_NONE } from '../../../lib/auth';

const localMessages = {
  notPermissioned: { id: 'topic.notPermissioned', defaultMessage: 'Missing Permission' },
};

const TopicListItem = (props) => {
  const { topic, onChangeFavorited } = props;
  const { formatMessage } = props.intl;
  const disable = false; // (topic.state !== 'ready');
  let title = null;
  if (disable || (topic.user_permission === PERMISSION_TOPIC_NONE)) {
    title = topic.name;
  } else {
    title = <Link to={`/topics/${topic.topics_id}/summary`}>{topic.name}</Link>;
  }
  let mainButton = null;
  if (topic.user_permission !== PERMISSION_TOPIC_NONE) {
    mainButton = (<FavoriteToggler
      isFavorited={topic.isFavorite}
      onChangeFavorited={isFavNow => onChangeFavorited(topic.topics_id, isFavNow)}
    />);
  } else {
    mainButton = (
      <IconButton tooltip={formatMessage(localMessages.notPermissioned)} >
        <Lock color="#FF8C00" />
      </IconButton>
    );
  }
  return (
    <Col xs={12} sm={6} md={6} lg={6}>
      <DataCard disabled={disable} inline >

{mainButton}

{title}

{topic.description}

{topic.state}

      </DataCard>
    </Col>
  );
};

TopicListItem.propTypes = {
  intl: React.PropTypes.object.isRequired,
  topic: React.PropTypes.object.isRequired,
  onChangeFavorited: React.PropTypes.func.isRequired,
};

export default injectIntl(TopicListItem);
<commit_msg>add status label to topic item in list<commit_after>import React from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import Link from 'react-router/lib/Link';
import { Col } from 'react-flexbox-grid/lib';
import Lock from 'material-ui/svg-icons/action/lock';
import IconButton from 'material-ui/IconButton';
import DataCard from '../../common/DataCard';
import FavoriteToggler from '../../common/FavoriteToggler';
import { PERMISSION_TOPIC_NONE } from '../../../lib/auth';

const localMessages = {
  notPermissioned: { id: 'topic.notPermissioned', defaultMessage: 'Missing Permission' },
  status: { id: 'topic.status', defaultMessage: 'Status: {status}' },
};

const TopicListItem = (props) => {
  const { topic, onChangeFavorited } = props;
  const { formatMessage } = props.intl;
  const disable = false; // (topic.state !== 'ready');
  let title = null;
  if (disable || (topic.user_permission === PERMISSION_TOPIC_NONE)) {
    title = topic.name;
  } else {
    title = <Link to={`/topics/${topic.topics_id}/summary`}>{topic.name}</Link>;
  }
  let mainButton = null;
  if (topic.user_permission !== PERMISSION_TOPIC_NONE) {
    mainButton = (<FavoriteToggler
      isFavorited={topic.isFavorite}
      onChangeFavorited={isFavNow => onChangeFavorited(topic.topics_id, isFavNow)}
    />);
  } else {
    mainButton = (
      <IconButton tooltip={formatMessage(localMessages.notPermissioned)} >
        <Lock color="#FF8C00" />
      </IconButton>
    );
  }
  return (
    <Col xs={12} sm={6} md={6} lg={6}>
      <DataCard disabled={disable} inline >

{mainButton}

{title}

{topic.description}

            <FormattedMessage {...localMessages.status} values={{ status: topic.state }} />

      </DataCard>
    </Col>
  );
};

TopicListItem.propTypes = {
  intl: React.PropTypes.object.isRequired,
  topic: React.PropTypes.object.isRequired,
  onChangeFavorited: React.PropTypes.func.isRequired,
};

export default injectIntl(TopicListItem);
<|endoftext|>


<commit_before>/* @flow */

import { isPrimitive } from '../util/index'
import VNode from './vnode'

export function normalizeChildren (
  children: any,
  ns: string | void
): Array<VNode> | void {
  // Invoke children thunks. Components always receive their children
  // as thunks so that they can perform the actual render inside their
  // own dependency collection cycle. Also, since JSX automatically
  // wraps component children in a thunk, we handle nested thunks to
  // prevent situations such as <MyComponent>{ children }</MyComponent>
  // from failing when it produces a double thunk.
  while (typeof children === 'function') {
    children = children()
  }

  if (isPrimitive(children)) {
    return [createTextVNode(children)]
  }
  if (Array.isArray(children)) {
    const res = []
    for (let i = 0, l = children.length; i < l; i++) {
      const c = children[i]
      const last = res[res.length - 1]
      //  nested
      if (Array.isArray(c)) {
        res.push.apply(res, normalizeChildren(c, ns))
      } else if (isPrimitive(c)) {
        if (last && last.text) {
          last.text += String(c)
        } else {
          // convert primitive to vnode
          res.push(createTextVNode(c))
        }
      } else if (c instanceof VNode) {
        if (c.text && last && last.text) {
          last.text += c.text
        } else {
          // inherit parent namespace
          if (ns) {
            applyNS(c, ns)
          }
          res.push(c)
        }
      }
    }
    return res
  }
}

function createTextVNode (val) {
  return new VNode(undefined, undefined, undefined, String(val))
}

function applyNS (vnode, ns) {
  if (vnode.tag && !vnode.ns) {
    vnode.ns = ns
    if (vnode.children) {
      for (let i = 0, l = vnode.children.length; i < l; i++) {
        applyNS(vnode.children[i], ns)
      }
    }
  }
}

// in case the child is also an abstract component, e.g. <transition-control>
// we want to recrusively retrieve the real component to be rendered
export function getRealChild (vnode: ?VNode): ?VNode {
  const compOptions = vnode && vnode.componentOptions
  if (compOptions && compOptions.Ctor.options.abstract) {
    return getRealChild(compOptions.propsData && compOptions.propsData.child)
  } else {
    return vnode
  }
}

export function mergeVNodeHook (def: Object, key: string, hook: Function) {
  const oldHook = def[key]
  if (oldHook) {
    def[key] = function () {
      oldHook.apply(this, arguments)
      hook.apply(this, arguments)
    }
  } else {
    def[key] = hook
  }
}

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function
) {
  let name, cur, old, fn, event, capture
  for (name in on) {
    cur = on[name]
    old = oldOn[name]
    if (!old) {
      capture = name.charAt(0) === '!'
      event = capture ? name.slice(1) : name
      if (Array.isArray(cur)) {
        add(event, (cur.invoker = arrInvoker(cur)), capture)
      } else {
        fn = cur
        cur = on[name] = {}
        cur.fn = fn
        add(event, (cur.invoker = fnInvoker(cur)), capture)
      }
    } else if (Array.isArray(old)) {
      old.length = cur.length
      for (let i = 0; i < old.length; i++) old[i] = cur[i]
      on[name] = old
    } else {
      old.fn = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (!on[name]) {
      event = name.charAt(0) === '!' ? name.slice(1) : name
      remove(event, oldOn[name].invoker)
    }
  }
}

function arrInvoker (arr: Array<Function>): Function {
  return function (ev) {
    const single = arguments.length === 1
    for (let i = 0; i < arr.length; i++) {
      single ? arr[i](ev) : arr[i].apply(null, arguments)
    }
  }
}

function fnInvoker (o: { fn: Function }): Function {
  return function (ev) {
    const single = arguments.length === 1
    single ? o.fn(ev) : o.fn.apply(null, arguments)
  }
}
<commit_msg>filter out empty strings when normalizing children (fix SSR match when containing text is empty)<commit_after>/* @flow */

import { isPrimitive } from '../util/index'
import VNode from './vnode'

export function normalizeChildren (
  children: any,
  ns: string | void
): Array<VNode> | void {
  // Invoke children thunks. Components always receive their children
  // as thunks so that they can perform the actual render inside their
  // own dependency collection cycle. Also, since JSX automatically
  // wraps component children in a thunk, we handle nested thunks to
  // prevent situations such as <MyComponent>{ children }</MyComponent>
  // from failing when it produces a double thunk.
  while (typeof children === 'function') {
    children = children()
  }

  if (isPrimitive(children)) {
    return [createTextVNode(children)]
  }
  if (Array.isArray(children)) {
    const res = []
    for (let i = 0, l = children.length; i < l; i++) {
      const c = children[i]
      const last = res[res.length - 1]
      //  nested
      if (Array.isArray(c)) {
        res.push.apply(res, normalizeChildren(c, ns))
      } else if (isPrimitive(c)) {
        if (last && last.text) {
          last.text += String(c)
        } else if (c !== '') {
          // convert primitive to vnode
          res.push(createTextVNode(c))
        }
      } else if (c instanceof VNode) {
        if (c.text && last && last.text) {
          last.text += c.text
        } else {
          // inherit parent namespace
          if (ns) {
            applyNS(c, ns)
          }
          res.push(c)
        }
      }
    }
    return res
  }
}

function createTextVNode (val) {
  return new VNode(undefined, undefined, undefined, String(val))
}

function applyNS (vnode, ns) {
  if (vnode.tag && !vnode.ns) {
    vnode.ns = ns
    if (vnode.children) {
      for (let i = 0, l = vnode.children.length; i < l; i++) {
        applyNS(vnode.children[i], ns)
      }
    }
  }
}

// in case the child is also an abstract component, e.g. <transition-control>
// we want to recrusively retrieve the real component to be rendered
export function getRealChild (vnode: ?VNode): ?VNode {
  const compOptions = vnode && vnode.componentOptions
  if (compOptions && compOptions.Ctor.options.abstract) {
    return getRealChild(compOptions.propsData && compOptions.propsData.child)
  } else {
    return vnode
  }
}

export function mergeVNodeHook (def: Object, key: string, hook: Function) {
  const oldHook = def[key]
  if (oldHook) {
    def[key] = function () {
      oldHook.apply(this, arguments)
      hook.apply(this, arguments)
    }
  } else {
    def[key] = hook
  }
}

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function
) {
  let name, cur, old, fn, event, capture
  for (name in on) {
    cur = on[name]
    old = oldOn[name]
    if (!old) {
      capture = name.charAt(0) === '!'
      event = capture ? name.slice(1) : name
      if (Array.isArray(cur)) {
        add(event, (cur.invoker = arrInvoker(cur)), capture)
      } else {
        fn = cur
        cur = on[name] = {}
        cur.fn = fn
        add(event, (cur.invoker = fnInvoker(cur)), capture)
      }
    } else if (Array.isArray(old)) {
      old.length = cur.length
      for (let i = 0; i < old.length; i++) old[i] = cur[i]
      on[name] = old
    } else {
      old.fn = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (!on[name]) {
      event = name.charAt(0) === '!' ? name.slice(1) : name
      remove(event, oldOn[name].invoker)
    }
  }
}

function arrInvoker (arr: Array<Function>): Function {
  return function (ev) {
    const single = arguments.length === 1
    for (let i = 0; i < arr.length; i++) {
      single ? arr[i](ev) : arr[i].apply(null, arguments)
    }
  }
}

function fnInvoker (o: { fn: Function }): Function {
  return function (ev) {
    const single = arguments.length === 1
    single ? o.fn(ev) : o.fn.apply(null, arguments)
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/* eslint-disable no-console */
var _ = require("lodash");
var csv = require("fast-csv");
var async = require("async");
var yaml = require("js-yaml");
var hyperpotamus = require("./lib");
var verror = require("verror");
var Promise = require("bluebird");

var defaultConfigFile = require("path").join(require("os").homedir(), ".hyperpotamus", "config.json");
var args = require("./cli/args")(defaultConfigFile);

// Setup logging configuration
var logger = hyperpotamus.logging.logger("hyperpotamus.cli");
hyperpotamus.logging.set_level(args.verbose + 3); // Starts at Warning, adding --verbose flags bumps to INFO, DEBUG, or TRACE

if(args.calfinated) {
	process.env.CALFINATED = "true";
}
execute(args);

function execute(args) {
	var sessionDefaults = require("./cli/sessionDefaults.js")(args);
	var emitStreams = require("./cli/emitStreams.js")(args);

	var processor = new hyperpotamus.Processor({ safe: args.safe, emit: emitStreams });

	try {
		if (args.plugins) {
			processor.plugins.loadPlugins(args.plugins);
		}
	}
	catch (err) {
		dumpError("loading plugins", err);
	}

	var script;
	try {
		script = processor.loadFile(args.file);
	}
	catch (err) {
		dumpError("loading yaml script", err);
	}

	// Pre-normalize script if we run it in a loop and for display/logging
	try {
		script = processor.normalize(script);
		logger.trace("Normalized script:\n" + yaml.dump(script));
		if (args.normalize) {
			showNormalized(script);
			process.exit(0);
		}
	}
	catch (err) {
		dumpError("normalizing script", err);
	}

	// Worker queue to process requests with set concurrency
	var queue = async.queue(function (session, callback) {
		logger.debug("About to start session for " + JSON.stringify(session));
		var localSession = _.merge({}, sessionDefaults, session);
		processor.process(script, localSession, args.start)
			.tap(context => logger.info("Final session data is:\n" + JSON.stringify(context.session, null, 2)))
			.delay(0) // To ensure we can SIGINT even if no async work happens in the script
			.then(callback)
			.catch(err => {
				dumpError("processing script", err);
			});
	}, args.concurrency);

	// Handler for graceful (or forced) shutdown
	var exiting = false;
	process.on("SIGINT", function () {
		if (!exiting) {
			exiting = true;
			queue.kill(); // Finish processing in-flight scripts, but stop any new ones from starting
			console.log("Gracefully shutting down from SIGINT (Press Ctrl-C again to exit immediately.)");
		}
		else {
			// Ok, tired of waiting, exit NOW!!
			process.exit(1);
		}
	});

	Promise.try(() => {
		if (args.init) {
			if (args.init === true) {
				args.init = "init";
			}

			// Run the script with the init steps
			return processor.process(script, sessionDefaults, args.init)
				.then(context => context.session)
				.catch(err => {
					dumpError("processing init script", err);
				});
		}
		return sessionDefaults;
	}).then(defaults => {
		sessionDefaults = defaults;
		if (args.csv) {
			logger.info("Loading data from csv file - " + args.csv);
			logger.info("Maximum concurrency level is " + args.concurrency);
			csv.fromPath(args.csv, { headers: true }).on("data", function (user) {
				if (!exiting) {
					queue.push(user);
					logger.trace("Queued user for processing " + JSON.stringify(user));
				}
			});
		}
		else if (args.loop) {
			var iterations = 0;
			// fill the queue up to max concurrency and whenever the queue is emptied (but still processing the current items), keep refilling until
			// we have finished. This prevents having to pre-queue a large (or potentially infinite) number of entries.
			queue.empty = () => {
				for (var i = 0; i < args.concurrency && (args.loop == true || iterations < args.loop); i++) {
					iterations++;
					queue.push({ "hyperpotamus.index": iterations });
					logger.trace("Queued user for processing #" + iterations);
				}
				if (args.loop === iterations) {
					// As soon as we have enough iterations, stop the refill. if args.loop == true it will never stop.
					queue.empty = _.noop;
				}
			};
			// Start filling
			queue.empty();
		}
		else {
			logger.trace("Processing script.");
			queue.push({});
		}
	});
}

function dumpError(message, err) {
	var cause = findCause(err);
	var dump = {
		name: cause.name,
		message: cause.message
	};
	var path = findDeepestInfoProperty(err, "path");
	if(!_.isNil(path)) {
		dump.path = path;
	}
	dump.help_link = `https://github.com/pmarkert/hyperpotamus/wiki/errors#${cause.name}`;
	logger.debug(`Error stack-trace:\n${verror.fullStack(err)}`);
	logger.info(`Error details:\n${yaml.dump(verror.info(err), { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	logger.error(`Error occurred while ${message}:\n${yaml.dump(dump, { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	process.exit(1);
}

function findCause(err) {
	var next;
	while((next = verror.cause(err))!=null) {
		if(next instanceof verror.VError) {
			err = next;
		}
		else {
			break;
		}
	}
	return err;
}

function findDeepestInfoProperty(err, property) {
	if(err instanceof verror.VError) {
		var result = findDeepestInfoProperty(verror.cause(err), property);
		return result || verror.info(err)[property];
	}
	return null;
}

function showNormalized(script) {
	console.log(`
Normalized JSON:
================
${JSON.stringify(script, null, 2)}

Normalized YAML:
================
${yaml.dump(script)}
`);
}
<commit_msg>Minor refactoring<commit_after>#!/usr/bin/env node
/* eslint-disable no-console */
var _ = require("lodash");
var csv = require("fast-csv");
var async = require("async");
var yaml = require("js-yaml");
var hyperpotamus = require("./lib");
var verror = require("verror");
var Promise = require("bluebird");

var defaultConfigFile = require("path").join(require("os").homedir(), ".hyperpotamus", "config.json");
var args = require("./cli/args")(defaultConfigFile);

// Setup logging configuration
var logger = hyperpotamus.logging.logger("hyperpotamus.cli");
hyperpotamus.logging.set_level(args.verbose + 3); // Starts at Warning, adding --verbose flags bumps to INFO, DEBUG, or TRACE

if(args.calfinated) {
	process.env.CALFINATED = "true";
}
execute(args);

function execute(args) {
	var sessionDefaults = require("./cli/sessionDefaults.js")(args);
	var emitStreams = require("./cli/emitStreams.js")(args);

	var processor = new hyperpotamus.Processor({ safe: args.safe, emit: emitStreams });

	try {
		if (args.plugins) {
			processor.plugins.loadPlugins(args.plugins);
		}
	}
	catch (err) {
		dumpError("loading plugins", err);
	}

	var script;
	try {
		script = processor.loadFile(args.file);
	}
	catch (err) {
		dumpError("loading yaml script", err);
	}

	// Pre-normalize script if we run it in a loop and for display/logging
	try {
		script = processor.normalize(script);
		logger.trace("Normalized script:\n" + yaml.dump(script));
		if (args.normalize) {
			showNormalized(script);
			process.exit(0);
		}
	}
	catch (err) {
		dumpError("normalizing script", err);
	}

	// Worker queue to process requests with set concurrency
	var queue = async.queue(function (session, callback) {
		logger.debug("About to start session for " + JSON.stringify(session));
		var localSession = _.merge({}, sessionDefaults, session);
		processor.process(script, localSession, args.start)
			.tap(context => logger.info("Final session data is:\n" + JSON.stringify(context.session, null, 2)))
			.delay(0) // To ensure we can SIGINT even if no async work happens in the script
			.then(callback)
			.catch(_.partial(dumpError, "processing script"));
	}, args.concurrency);

	// Handler for graceful (or forced) shutdown
	var exiting = false;
	process.on("SIGINT", function () {
		if (!exiting) {
			exiting = true;
			queue.kill(); // Finish processing in-flight scripts, but stop any new ones from starting
			console.log("Gracefully shutting down from SIGINT (Press Ctrl-C again to exit immediately.)");
		}
		else {
			// Ok, tired of waiting, exit NOW!!
			process.exit(1);
		}
	});

	Promise.try(() => {
		if (args.init) {
			if (args.init === true) {
				args.init = "init";
			}

			// Run the script with the init steps
			return processor.process(script, sessionDefaults, args.init)
				.then(context => context.session)
				.catch(_.partial(dumpError, "processing init script"));
		}
		else {
			return sessionDefaults;
		}
	}).then(defaults => {
		sessionDefaults = defaults;
		if (args.csv) {
			logger.info("Loading data from csv file - " + args.csv);
			logger.info("Maximum concurrency level is " + args.concurrency);
			csv.fromPath(args.csv, { headers: true }).on("data", function (user) {
				if (!exiting) {
					queue.push(user);
					logger.trace("Queued user for processing " + JSON.stringify(user));
				}
			});
		}
		else if (args.loop) {
			var iterations = 0;
			// fill the queue up to max concurrency and whenever the queue is emptied (but still processing the current items), keep refilling until
			// we have finished. This prevents having to pre-queue a large (or potentially infinite) number of entries.
			queue.empty = () => {
				for (var i = 0; i < args.concurrency && (args.loop == true || iterations < args.loop); i++) {
					iterations++;
					queue.push({ "hyperpotamus.index": iterations });
					logger.trace("Queued user for processing #" + iterations);
				}
				if (args.loop === iterations) {
					// As soon as we have enough iterations, stop the refill. if args.loop == true it will never stop.
					queue.empty = _.noop;
				}
			};
			// Start filling
			queue.empty();
		}
		else {
			logger.trace("Processing script.");
			queue.push({});
		}
	});
}

function dumpError(message, err) {
	var cause = findCause(err);
	var dump = {
		name: cause.name,
		message: cause.message
	};
	var path = findDeepestInfoProperty(err, "path");
	if(!_.isNil(path)) {
		dump.path = path;
	}
	dump.help_link = `https://github.com/pmarkert/hyperpotamus/wiki/errors#${cause.name}`;
	logger.debug(`Error stack-trace:\n${verror.fullStack(err)}`);
	logger.info(`Error details:\n${yaml.dump(verror.info(err), { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	logger.error(`Error occurred while ${message}:\n${yaml.dump(dump, { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	process.exit(1);
}

function findCause(err) {
	var next;
	while((next = verror.cause(err))!=null) {
		if(next instanceof verror.VError) {
			err = next;
		}
		else {
			break;
		}
	}
	return err;
}

function findDeepestInfoProperty(err, property) {
	if(err instanceof verror.VError) {
		var result = findDeepestInfoProperty(verror.cause(err), property);
		return result || verror.info(err)[property];
	}
	return null;
}

function showNormalized(script) {
	console.log(`
Normalized JSON:
================
${JSON.stringify(script, null, 2)}

Normalized YAML:
================
${yaml.dump(script)}
`);
}
<|endoftext|>


<commit_before><commit_msg>Update project goodshare.js to 3.1.2<commit_after><|endoftext|>


<commit_before>define(["cmwapi/cmwapi", "cmwapi-adapter/ViewUtils"], function(cmwapi, ViewUtils) {
    /**
     * @copyright © 2013 Environmental Systems Research Institute, Inc. (Esri)
     *
     * @license
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at

     *

     *     {@link http://www.apache.org/licenses/LICENSE-2.0}

     *

     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     * @description Manager for overlay layers to be used in conjunction with an ESRI map,
     * the {@link EsriAdapter}, and the {@link Map|Common Map Widget API}
     *
     * @version 1.1
     *
     * @module cmwapi-adapter/EsriOverlayManager/Overlay
     */

     var handlers = function(manager, map, adapter) {
        var me = this;

        /**
         * An overlay layer to contain features.
         * @constructor
         * @param overlayId {String} the unique id by which this overlay should be identified
         * @param name {String} optional; the non-unique readable name; if not specified the id will be used
         * @param parentId {String} optional; the parent to be set for this overlay
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay
         */
        var Overlay = function(overlayId, name, parentId) {
            this.id= overlayId;
            this.name= name || '';
            //Overlay IDs of children overlays
            this.children = {};

            //Mapping FeatureID->Feature object of children features
            this.features = {};
            this.isHidden = false;
            if(parentId) {
                this.parentId = parentId;
                resolveParent(this, parentId);
            }
        };

        var sendError = function(caller, msg, error) {
            var sender = caller;
            var type = error.type;

            cmwapi.error.send(sender, type, msg, error);
        };

        /**
         * @private
         * @method resolveParent
         * @param childId {String} The id of the child for which the parent should be set
         * @param parentId {String} The id of the overlay to be set as parent
         * @param previousParentId {String} optional; The id of the overlay that was previously designated as parent to the childId
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        var resolveParent = function(child, parentId, previousParentId) {
            if(previousParentId) {
                delete manager.overlays[previousParentId].children[child.id];
            }

            if(manager.overlays[parentId]) {
                manager.overlays[parentId].children[child.id] = child;
            }
        };

        /**
         * This function will create a new overlay in a non-destructive manner.  If an overlay with the
         * given overlayId already exists, this function will have no effect.
         * @method createOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param name {String} optional; The readable name for the overlay; if not specified the id will be used
         * @param overlayId {String} The id of the overlay to create; if it exists nothing will be created
         * @param parentId {String} The id of the overlay to be set as the parent of the overlay being created.
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.createOverlay = function(caller, overlayId, name, parentId) {
            if(!manager.overlays[overlayId]) {
                manager.overlays[overlayId] = new Overlay(overlayId, name, parentId);
                manager.treeChanged();
            }
        };

        /**
         * @method deleteOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be deleted from the manager
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.removeOverlay = function(caller, overlayId) {
            //TODO Error if overlay not found?

            var overlay = manager.overlays[overlayId];

            if(overlay) {
                //find parent
                var parent = manager.overlays[overlay.parentId];
                if(parent) {
                    delete parent.children[overlayId];
                }

                var features = overlay.features;
                for(var i in features) {
                    if(features.hasOwnProperty(i)) {
                        manager.feature.deleteFeature(caller, overlayId, features[i].featureId);
                    }
                }

                var children = overlay.children;
                //recursively remove children from this overlay
                for(i in children) {
                    if(children.hasOwnProperty(i)) {
                        me.removeOverlay(caller, i);
                    }
                }
            }

            delete manager.overlays[overlayId];
            manager.treeChanged();
        };

        /**
         * @method hideOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be hidden
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.hideOverlay = function(caller, overlayId) {
            var overlay = manager.overlays[overlayId];
            if(!overlay) {
                var msg = "Overlay not found with id " + overlayId;
                sendError(caller, msg, {type: "map.overlay.hide", msg: msg});
            } else {
                overlay.isHidden = true;

                for(var i in overlay.features) {
                    if(overlay.features.hasOwnProperty(i)) {
                        overlay.features[i].isHidden = true;
                        overlay.features[i].esriObject.hide();
                    }
                }

                for(var o in overlay.children) {
                    if(overlay.children.hasOwnProperty(o)) {
                        me.hideOverlay(caller, o);
                    }
                }

                manager.treeChanged();
            }
        };

        /**
         * @method showOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be shown
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.showOverlay = function(caller, overlayId) {
            var overlay = manager.overlays[overlayId];
            if(!overlay) {
                var msg = "Overlay not found with id " + overlayId;
                sendError(caller, msg, {type: "map.overlay.show", msg: msg});
            } else {
                overlay.isHidden = false;

                var features = overlay.features;
                for(var i in features) {
                    if(features.hasOwnProperty(i)) {
                        features[i].isHidden = false;
                        features[i].esriObject.show();
                    }
                }

                for(var o in overlay.children) {
                    if(overlay.children.hasOwnProperty(o)) {
                        me.showOverlay(caller, o);
                    }
                }
                manager.treeChanged();
            }
        };

        /**
         * @method updateOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be updated.
         * @param name {String} The name that should be set; the current or a new name.
         * @param parentId {String} optional; the id of the overlay to be set as the parent.
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.updateOverlay = function(caller, overlayId, name, parentId) {
            if(typeof(manager.overlays[overlayId]) === 'undefined') {
                var msg = "No overlay exists with the provided id of " + overlayId;
                sendError(caller, msg, {type: 'map.overlay.update', msg: msg});
            } else {
                var overlay = manager.overlays[overlayId];
                if(name && overlay.name !== name) {
                    overlay.name = name;
                }
                if(parentId && parentId !== overlay.parentId) {
                    var oldParent = overlay.parentId;
                    overlay.parentId = parentId;
                    resolveParent(overlay, parentId, oldParent);
                }
                manager.treeChanged();
            }
        };

        /**
         * @method zoom
         * @param caller {String}
         * @param overlayId {String}
         * @param zoom {number}
         */
        me.zoom = function(caller, overlayId, range) {
            var overlay = manager.overlays[overlayId];
            var msg;
            if(typeof(overlay) === 'undefined') {
                msg = "Overlay could not be found with id " + overlayId;
                sendError(caller, msg, {type: "map.feature.zoom", msg: msg});
                return;
            }

            var extent = ViewUtils.findOverlayExtent(overlay);

            if(extent) {
                // If auto zoom, reset the entire extent.
                if (range && range.toString().toLowerCase() === "auto") {
                    map.setExtent(extent, true);
                }
                // If we have a non-auto zoom, recenter the map and zoom.
                else if (typeof range !== "undefined") {
                    // Set the zoom level.
                    map.setScale(ViewUtils.zoomAltitudeToScale(map, range));

                    // Recenter the map.
                    map.centerAt(extent.getCenter());
                }
                // Otherwise, use recenter the map.
                else {
                    map.centerAt(extent.getCenter());
                }
            }
        };
    };

    return handlers;
});<commit_msg>Correcting a doc block so a method wasn't flagged as a Global.<commit_after>define(["cmwapi/cmwapi", "cmwapi-adapter/ViewUtils"], function(cmwapi, ViewUtils) {
    /**
     * @copyright © 2013 Environmental Systems Research Institute, Inc. (Esri)
     *
     * @license
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at

     *

     *     {@link http://www.apache.org/licenses/LICENSE-2.0}

     *

     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     * @description Manager for overlay layers to be used in conjunction with an ESRI map,
     * the {@link EsriAdapter}, and the {@link Map|Common Map Widget API}
     *
     * @version 1.1
     *
     * @module cmwapi-adapter/EsriOverlayManager/Overlay
     */

     var handlers = function(manager, map, adapter) {
        var me = this;

        /**
         * An overlay layer to contain features.
         * @constructor
         * @param overlayId {String} the unique id by which this overlay should be identified
         * @param name {String} optional; the non-unique readable name; if not specified the id will be used
         * @param parentId {String} optional; the parent to be set for this overlay
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay
         */
        var Overlay = function(overlayId, name, parentId) {
            this.id= overlayId;
            this.name= name || '';
            //Overlay IDs of children overlays
            this.children = {};

            //Mapping FeatureID->Feature object of children features
            this.features = {};
            this.isHidden = false;
            if(parentId) {
                this.parentId = parentId;
                resolveParent(this, parentId);
            }
        };

        var sendError = function(caller, msg, error) {
            var sender = caller;
            var type = error.type;

            cmwapi.error.send(sender, type, msg, error);
        };

        /**
         * @private
         * @method resolveParent
         * @param childId {String} The id of the child for which the parent should be set
         * @param parentId {String} The id of the overlay to be set as parent
         * @param previousParentId {String} optional; The id of the overlay that was previously designated as parent to the childId
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        var resolveParent = function(child, parentId, previousParentId) {
            if(previousParentId) {
                delete manager.overlays[previousParentId].children[child.id];
            }

            if(manager.overlays[parentId]) {
                manager.overlays[parentId].children[child.id] = child;
            }
        };

        /**
         * This function will create a new overlay in a non-destructive manner.  If an overlay with the
         * given overlayId already exists, this function will have no effect.
         * @method createOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param name {String} optional; The readable name for the overlay; if not specified the id will be used
         * @param overlayId {String} The id of the overlay to create; if it exists nothing will be created
         * @param parentId {String} The id of the overlay to be set as the parent of the overlay being created.
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.createOverlay = function(caller, overlayId, name, parentId) {
            if(!manager.overlays[overlayId]) {
                manager.overlays[overlayId] = new Overlay(overlayId, name, parentId);
                manager.treeChanged();
            }
        };

        /**
         * @method deleteOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be deleted from the manager
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.removeOverlay = function(caller, overlayId) {
            //TODO Error if overlay not found?

            var overlay = manager.overlays[overlayId];

            if(overlay) {
                //find parent
                var parent = manager.overlays[overlay.parentId];
                if(parent) {
                    delete parent.children[overlayId];
                }

                var features = overlay.features;
                for(var i in features) {
                    if(features.hasOwnProperty(i)) {
                        manager.feature.deleteFeature(caller, overlayId, features[i].featureId);
                    }
                }

                var children = overlay.children;
                //recursively remove children from this overlay
                for(i in children) {
                    if(children.hasOwnProperty(i)) {
                        me.removeOverlay(caller, i);
                    }
                }
            }

            delete manager.overlays[overlayId];
            manager.treeChanged();
        };

        /**
         * @method hideOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be hidden
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.hideOverlay = function(caller, overlayId) {
            var overlay = manager.overlays[overlayId];
            if(!overlay) {
                var msg = "Overlay not found with id " + overlayId;
                sendError(caller, msg, {type: "map.overlay.hide", msg: msg});
            } else {
                overlay.isHidden = true;

                for(var i in overlay.features) {
                    if(overlay.features.hasOwnProperty(i)) {
                        overlay.features[i].isHidden = true;
                        overlay.features[i].esriObject.hide();
                    }
                }

                for(var o in overlay.children) {
                    if(overlay.children.hasOwnProperty(o)) {
                        me.hideOverlay(caller, o);
                    }
                }

                manager.treeChanged();
            }
        };

        /**
         * @method showOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be shown
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.showOverlay = function(caller, overlayId) {
            var overlay = manager.overlays[overlayId];
            if(!overlay) {
                var msg = "Overlay not found with id " + overlayId;
                sendError(caller, msg, {type: "map.overlay.show", msg: msg});
            } else {
                overlay.isHidden = false;

                var features = overlay.features;
                for(var i in features) {
                    if(features.hasOwnProperty(i)) {
                        features[i].isHidden = false;
                        features[i].esriObject.show();
                    }
                }

                for(var o in overlay.children) {
                    if(overlay.children.hasOwnProperty(o)) {
                        me.showOverlay(caller, o);
                    }
                }
                manager.treeChanged();
            }
        };

        /**
         * @method updateOverlay
         * @param caller {String} the id of the widget which made the request resulting in this function call.
         * @param overlayId {String} the id of the overlay to be updated.
         * @param name {String} The name that should be set; the current or a new name.
         * @param parentId {String} optional; the id of the overlay to be set as the parent.
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.updateOverlay = function(caller, overlayId, name, parentId) {
            if(typeof(manager.overlays[overlayId]) === 'undefined') {
                var msg = "No overlay exists with the provided id of " + overlayId;
                sendError(caller, msg, {type: 'map.overlay.update', msg: msg});
            } else {
                var overlay = manager.overlays[overlayId];
                if(name && overlay.name !== name) {
                    overlay.name = name;
                }
                if(parentId && parentId !== overlay.parentId) {
                    var oldParent = overlay.parentId;
                    overlay.parentId = parentId;
                    resolveParent(overlay, parentId, oldParent);
                }
                manager.treeChanged();
            }
        };

        /**
         * @method zoom
         * @param caller {String}
         * @param overlayId {String}
         * @param zoom {number}
         * @memberof module:cmwapi-adapter/EsriOverlayManager/Overlay#
         */
        me.zoom = function(caller, overlayId, range) {
            var overlay = manager.overlays[overlayId];
            var msg;
            if(typeof(overlay) === 'undefined') {
                msg = "Overlay could not be found with id " + overlayId;
                sendError(caller, msg, {type: "map.feature.zoom", msg: msg});
                return;
            }

            var extent = ViewUtils.findOverlayExtent(overlay);

            if(extent) {
                // If auto zoom, reset the entire extent.
                if (range && range.toString().toLowerCase() === "auto") {
                    map.setExtent(extent, true);
                }
                // If we have a non-auto zoom, recenter the map and zoom.
                else if (typeof range !== "undefined") {
                    // Set the zoom level.
                    map.setScale(ViewUtils.zoomAltitudeToScale(map, range));

                    // Recenter the map.
                    map.centerAt(extent.getCenter());
                }
                // Otherwise, use recenter the map.
                else {
                    map.centerAt(extent.getCenter());
                }
            }
        };
    };

    return handlers;
});<|endoftext|>