<commit_before>/**
 * @module test/index.spec
 */
'use strict';

var Code = require('code');
var Lab = require('lab');
var rewire = require('rewire');
var sinon = require('sinon');
var defaults = require('101/defaults');

var Hermes = rewire('../index');

var connectionOpts = require('./fixtures/connection-opts');
var mockChannel = require('./fixtures/mock-channel');
var mockConnection = require('./fixtures/create-mock-connection');

var lab = exports.lab = Lab.script();

var after = lab.after;
var afterEach = lab.afterEach;
var before = lab.before;
var beforeEach = lab.beforeEach;
var describe = lab.describe;
var expect = Code.expect;
var it = lab.it;

describe('hermes', function () {
  var HermesClass = Hermes.__get__('Hermes');

  it('should initiate a connection to a rabbitmq server on instantiate', function (done) {
    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net:1111?heartbeat=0');
      hermesAmqplib.connect.restore();
      done();
    });
    var hermes = Hermes.hermesSingletonFactory(connectionOpts.standard);
    hermes.connect();
  });

  it('should correctly construct connection url string without a specified port', function (done) {
    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net?heartbeat=0');
      hermesAmqplib.connect.restore();
      done();
    });
    var hermes = new HermesClass(connectionOpts.noSpecPort);
    hermes.connect();
  });

  it('should set the heartbeat duration based on the environment', function (done) {
    var oldHeartbeat = process.env.RABBITMQ_HEARTBEAT;
    process.env.RABBITMQ_HEARTBEAT = 1337;

    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net:1111?heartbeat=1337');
      hermesAmqplib.connect.restore();
      process.env.RABBITMQ_HEARTBEAT = oldHeartbeat;
      done();
    });

    var hermes = new Hermes(connectionOpts.standard);
    hermes.connect();
  });

  it('should set the hearbeat based on passed socket options', function(done) {
    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net:1111?heartbeat=5555');
      hermesAmqplib.connect.restore();
      done();
    });
    var hermes = new Hermes(connectionOpts.standard, { heartbeat: 5555 });
    hermes.connect();
  });

  describe('pre-connect and post-connect functionality', function () {
    var TEST_QUEUE = 'test-queue';
    var connection;
    var channel;
    var connectFinish;
    var hermes;
    var hermesAmqplib;

    beforeEach(function (done) {
      hermesAmqplib = Hermes.__get__('amqplib');
      // connectFinish allow testing pre-post connected states
      sinon.stub(hermesAmqplib, 'connect', function (url, socketOpts, cb) {
        connectFinish = function () {
          connection = mockConnection();
          connection.createChannel = function (cb) {
            channel = mockChannel();
            cb(null, channel);
          };
          cb(null, connection);
        };
      });

      var opts = { queues: [TEST_QUEUE] };
      defaults(opts, connectionOpts.standard);
      hermes = new HermesClass(opts);
      hermes.connect();

      done();
    });

    afterEach(function (done) {
      hermesAmqplib.connect.restore();
      done();
    });

    it('should automatically queue subscribe invocations until connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var subscribeCB = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, subscribeCB);
      expect(hermes.subscribeQueue).to.have.length(1);
      // simulate connection complete
      connectFinish();
      // all queued subscribe jobs are complete
      expect(hermes.subscribeQueue).to.have.length(0);
      done();
    });

    it('should automatically queue publish invocations until connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var testData = {foo: 'bar'};
      hermes.publish(TEST_QUEUE, testData);
      expect(hermes.publishQueue).to.have.length(1);
      // simulate connection complete
      connectFinish();
      // all queued subscribe jobs are complete
      expect(hermes.publishQueue).to.have.length(0);
      done();
    });

    it('should not queue publish invocations if already connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      connectFinish();
      // connected...
      var testData = {foo: 'bar'};
      expect(hermes.publishQueue).to.have.length(0);
      hermes.publish(TEST_QUEUE, testData);
      expect(hermes.publishQueue).to.have.length(0);
      expect(channel.sendToQueue.callCount).to.equal(1);
      expect(channel.sendToQueue.args[0][0]).to.equal(TEST_QUEUE);
      expect(channel.sendToQueue.args[0][1].toString())
        .to.equal(new Buffer(JSON.stringify(testData)).toString());
      done();
    });

    it('should not queue subscribe invocations if already connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      connectFinish();
      // connected...
      var subscribeCB = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, subscribeCB);
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(channel.consume.callCount).to.equal(1);
      expect(channel.consume.args[0][0]).to.equal(TEST_QUEUE);
      done();
    });

    it('should remove workers from subscribe queue on unsubscribe if not yet connected (all workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      expect(hermes.subscribeQueue).to.have.length(1);
      // only has consumerTag if registered w/ rabbitmq
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      hermes.unsubscribe(TEST_QUEUE, null, callback);
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should remove workers from subscribe queue on unsubscribe if not yet connected (specific workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      var worker2 = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      hermes.subscribe(TEST_QUEUE, worker2);
      expect(hermes.subscribeQueue).to.have.length(2);
      // only has consumerTag if registered w/ rabbitmq
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      hermes.unsubscribe(TEST_QUEUE, worker, callback);
      expect(hermes.subscribeQueue).to.have.length(1);
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should unsubscribe workers from rabbitmq (all workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      var worker2 = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      hermes.subscribe(TEST_QUEUE, worker2);
      expect(hermes.subscribeQueue).to.have.length(2);
      connectFinish();
      // connected...
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(Object.keys(hermes.consumerTags)).to.have.length(2);
      var consumerTag = Object.keys(hermes.consumerTags)[0];
      var consumerTag2 = Object.keys(hermes.consumerTags)[1];
      hermes.unsubscribe(TEST_QUEUE, null, callback);
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      expect(channel.cancel.callCount).to.equal(2);
      expect(channel.cancel.args[0][0]).to.equal(consumerTag);
      expect(channel.cancel.args[1][0]).to.equal(consumerTag2);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should unsubscribe workers from rabbitmq (specific workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      var worker2 = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      hermes.subscribe(TEST_QUEUE, worker2);
      expect(hermes.subscribeQueue).to.have.length(2);
      connectFinish();
      // connected...
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(Object.keys(hermes.consumerTags)).to.have.length(2);
      var consumerTag = Object.keys(hermes.consumerTags)[1];
      hermes.unsubscribe(TEST_QUEUE, worker, callback);
      expect(Object.keys(hermes.consumerTags)).to.have.length(1);
      expect(channel.cancel.callCount).to.equal(1);
      expect(channel.cancel.args[0][0]).to.equal(consumerTag);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should emit channel error to hermes', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      connectFinish();
      // connected...
      hermes.on('error', function (err) {
        expect(err.message).to.equal('Some channel error');
        expect(err.reason).to.equal('channel error');
        done();
      });
      channel.emit('error', new Error('Some channel error'));
    });

    it('should emit connection error to hermes', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      connectFinish();
      // connected...
      hermes.on('error', function (err) {
        expect(err.message).to.equal('Some connection error');
        expect(err.reason).to.equal('connection error');
        done();
      });
      connection.emit('error', new Error('Some connection error'));
    });

    describe('#persistent option', function () {
      beforeEach(function (done) {
        connectFinish();
        channel.origSendToQueue = channel.sendToQueue;
        channel.sendToQueue = sinon.spy(function (queueName, data, opts) {
          expect(opts.persistent).to.equal(true);
        });
        done();
      });

      afterEach(function (done) {
        channel.sendToQueue = channel.origSendToQueue;
        delete channel.origSendToQueue;
        done();
      });

      it('should send messages with true persistent opt', function (done) {
        //connectFinish();
        hermes.publish(TEST_QUEUE, {foo: 'bar'});
        done();
      });
    });

    describe('#_subscribeCallback', function () {
      it('should emit an error if channel is null', function (done) {
        var hermes = new Hermes(connectionOpts.standard);
        hermes.on('error', function (err) {
          expect(err).to.exist();
          expect(err.message).to.equal('Cannot ack. Channel does not exist');
          done();
        });
        hermes._subscribeCallback(function (message, subscribeCb) {
          expect(message.name).to.equal('job1');
          subscribeCb();
        })({content: JSON.stringify({name: 'job1'})});
      });
      it('should call ack and callback', function (done) {
        var msg = {
          content: JSON.stringify({name: 'job1'})
        };
        sinon.spy(JSON, 'parse');
        var hermes = new Hermes(connectionOpts.standard);
        hermes._channel = {
          ack: function (message) {
            expect(message).to.equal(msg);
            expect(JSON.parse.callCount).to.equal(1);
            JSON.parse.restore();
            done();
          }
        };
        hermes.on('error', function (err) {
          done(err);
        });
        hermes._subscribeCallback(function (message, subscribeCb) {
          expect(message.name).to.equal('job1');
          subscribeCb();
        })(msg);
      });
    });
  });
});
<commit_msg>Remove unused commented code from test<commit_after>/**
 * @module test/index.spec
 */
'use strict';

var Code = require('code');
var Lab = require('lab');
var rewire = require('rewire');
var sinon = require('sinon');
var defaults = require('101/defaults');

var Hermes = rewire('../index');

var connectionOpts = require('./fixtures/connection-opts');
var mockChannel = require('./fixtures/mock-channel');
var mockConnection = require('./fixtures/create-mock-connection');

var lab = exports.lab = Lab.script();

var after = lab.after;
var afterEach = lab.afterEach;
var before = lab.before;
var beforeEach = lab.beforeEach;
var describe = lab.describe;
var expect = Code.expect;
var it = lab.it;

describe('hermes', function () {
  var HermesClass = Hermes.__get__('Hermes');

  it('should initiate a connection to a rabbitmq server on instantiate', function (done) {
    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net:1111?heartbeat=0');
      hermesAmqplib.connect.restore();
      done();
    });
    var hermes = Hermes.hermesSingletonFactory(connectionOpts.standard);
    hermes.connect();
  });

  it('should correctly construct connection url string without a specified port', function (done) {
    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net?heartbeat=0');
      hermesAmqplib.connect.restore();
      done();
    });
    var hermes = new HermesClass(connectionOpts.noSpecPort);
    hermes.connect();
  });

  it('should set the heartbeat duration based on the environment', function (done) {
    var oldHeartbeat = process.env.RABBITMQ_HEARTBEAT;
    process.env.RABBITMQ_HEARTBEAT = 1337;

    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net:1111?heartbeat=1337');
      hermesAmqplib.connect.restore();
      process.env.RABBITMQ_HEARTBEAT = oldHeartbeat;
      done();
    });

    var hermes = new Hermes(connectionOpts.standard);
    hermes.connect();
  });

  it('should set the hearbeat based on passed socket options', function(done) {
    var hermesAmqplib = Hermes.__get__('amqplib');
    sinon.stub(hermesAmqplib, 'connect', function (url) {
      expect(url).to.be.a.string();
      expect(url).to.equal('amqp://tom:harry@bobsburgers.net:1111?heartbeat=5555');
      hermesAmqplib.connect.restore();
      done();
    });
    var hermes = new Hermes(connectionOpts.standard, { heartbeat: 5555 });
    hermes.connect();
  });

  describe('pre-connect and post-connect functionality', function () {
    var TEST_QUEUE = 'test-queue';
    var connection;
    var channel;
    var connectFinish;
    var hermes;
    var hermesAmqplib;

    beforeEach(function (done) {
      hermesAmqplib = Hermes.__get__('amqplib');
      // connectFinish allow testing pre-post connected states
      sinon.stub(hermesAmqplib, 'connect', function (url, socketOpts, cb) {
        connectFinish = function () {
          connection = mockConnection();
          connection.createChannel = function (cb) {
            channel = mockChannel();
            cb(null, channel);
          };
          cb(null, connection);
        };
      });

      var opts = { queues: [TEST_QUEUE] };
      defaults(opts, connectionOpts.standard);
      hermes = new HermesClass(opts);
      hermes.connect();

      done();
    });

    afterEach(function (done) {
      hermesAmqplib.connect.restore();
      done();
    });

    it('should automatically queue subscribe invocations until connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var subscribeCB = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, subscribeCB);
      expect(hermes.subscribeQueue).to.have.length(1);
      // simulate connection complete
      connectFinish();
      // all queued subscribe jobs are complete
      expect(hermes.subscribeQueue).to.have.length(0);
      done();
    });

    it('should automatically queue publish invocations until connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var testData = {foo: 'bar'};
      hermes.publish(TEST_QUEUE, testData);
      expect(hermes.publishQueue).to.have.length(1);
      // simulate connection complete
      connectFinish();
      // all queued subscribe jobs are complete
      expect(hermes.publishQueue).to.have.length(0);
      done();
    });

    it('should not queue publish invocations if already connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      connectFinish();
      // connected...
      var testData = {foo: 'bar'};
      expect(hermes.publishQueue).to.have.length(0);
      hermes.publish(TEST_QUEUE, testData);
      expect(hermes.publishQueue).to.have.length(0);
      expect(channel.sendToQueue.callCount).to.equal(1);
      expect(channel.sendToQueue.args[0][0]).to.equal(TEST_QUEUE);
      expect(channel.sendToQueue.args[0][1].toString())
        .to.equal(new Buffer(JSON.stringify(testData)).toString());
      done();
    });

    it('should not queue subscribe invocations if already connected to RabbitMQ server', function (done) {
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      connectFinish();
      // connected...
      var subscribeCB = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, subscribeCB);
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(channel.consume.callCount).to.equal(1);
      expect(channel.consume.args[0][0]).to.equal(TEST_QUEUE);
      done();
    });

    it('should remove workers from subscribe queue on unsubscribe if not yet connected (all workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      expect(hermes.subscribeQueue).to.have.length(1);
      // only has consumerTag if registered w/ rabbitmq
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      hermes.unsubscribe(TEST_QUEUE, null, callback);
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should remove workers from subscribe queue on unsubscribe if not yet connected (specific workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      var worker2 = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      hermes.subscribe(TEST_QUEUE, worker2);
      expect(hermes.subscribeQueue).to.have.length(2);
      // only has consumerTag if registered w/ rabbitmq
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      hermes.unsubscribe(TEST_QUEUE, worker, callback);
      expect(hermes.subscribeQueue).to.have.length(1);
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should unsubscribe workers from rabbitmq (all workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      var worker2 = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      hermes.subscribe(TEST_QUEUE, worker2);
      expect(hermes.subscribeQueue).to.have.length(2);
      connectFinish();
      // connected...
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(Object.keys(hermes.consumerTags)).to.have.length(2);
      var consumerTag = Object.keys(hermes.consumerTags)[0];
      var consumerTag2 = Object.keys(hermes.consumerTags)[1];
      hermes.unsubscribe(TEST_QUEUE, null, callback);
      expect(Object.keys(hermes.consumerTags)).to.have.length(0);
      expect(channel.cancel.callCount).to.equal(2);
      expect(channel.cancel.args[0][0]).to.equal(consumerTag);
      expect(channel.cancel.args[1][0]).to.equal(consumerTag2);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should unsubscribe workers from rabbitmq (specific workers in queue)', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      var worker = function (data, done) {};
      var worker2 = function (data, done) {};
      hermes.subscribe(TEST_QUEUE, worker);
      hermes.subscribe(TEST_QUEUE, worker2);
      expect(hermes.subscribeQueue).to.have.length(2);
      connectFinish();
      // connected...
      expect(hermes.subscribeQueue).to.have.length(0);
      expect(Object.keys(hermes.consumerTags)).to.have.length(2);
      var consumerTag = Object.keys(hermes.consumerTags)[1];
      hermes.unsubscribe(TEST_QUEUE, worker, callback);
      expect(Object.keys(hermes.consumerTags)).to.have.length(1);
      expect(channel.cancel.callCount).to.equal(1);
      expect(channel.cancel.args[0][0]).to.equal(consumerTag);
      expect(callback.callCount).to.equal(1);
      done();
    });

    it('should emit channel error to hermes', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      connectFinish();
      // connected...
      hermes.on('error', function (err) {
        expect(err.message).to.equal('Some channel error');
        expect(err.reason).to.equal('channel error');
        done();
      });
      channel.emit('error', new Error('Some channel error'));
    });

    it('should emit connection error to hermes', function (done) {
      var callback = sinon.spy();
      expect(hermesAmqplib.connect.callCount).to.equal(1);
      // not yet connected...
      connectFinish();
      // connected...
      hermes.on('error', function (err) {
        expect(err.message).to.equal('Some connection error');
        expect(err.reason).to.equal('connection error');
        done();
      });
      connection.emit('error', new Error('Some connection error'));
    });

    describe('#persistent option', function () {
      beforeEach(function (done) {
        connectFinish();
        channel.origSendToQueue = channel.sendToQueue;
        channel.sendToQueue = sinon.spy(function (queueName, data, opts) {
          expect(opts.persistent).to.equal(true);
        });
        done();
      });

      afterEach(function (done) {
        channel.sendToQueue = channel.origSendToQueue;
        delete channel.origSendToQueue;
        done();
      });

      it('should send messages with true persistent opt', function (done) {
        hermes.publish(TEST_QUEUE, {foo: 'bar'});
        done();
      });
    });

    describe('#_subscribeCallback', function () {
      it('should emit an error if channel is null', function (done) {
        var hermes = new Hermes(connectionOpts.standard);
        hermes.on('error', function (err) {
          expect(err).to.exist();
          expect(err.message).to.equal('Cannot ack. Channel does not exist');
          done();
        });
        hermes._subscribeCallback(function (message, subscribeCb) {
          expect(message.name).to.equal('job1');
          subscribeCb();
        })({content: JSON.stringify({name: 'job1'})});
      });
      it('should call ack and callback', function (done) {
        var msg = {
          content: JSON.stringify({name: 'job1'})
        };
        sinon.spy(JSON, 'parse');
        var hermes = new Hermes(connectionOpts.standard);
        hermes._channel = {
          ack: function (message) {
            expect(message).to.equal(msg);
            expect(JSON.parse.callCount).to.equal(1);
            JSON.parse.restore();
            done();
          }
        };
        hermes.on('error', function (err) {
          done(err);
        });
        hermes._subscribeCallback(function (message, subscribeCb) {
          expect(message.name).to.equal('job1');
          subscribeCb();
        })(msg);
      });
    });
  });
});
<|endoftext|>


<commit_before>$(function() {
    'use strict';

    Dingbat.View.Navigation = Backbone.Layout.extend({

        template: '#navigation-template',

        activeCard: null,

        events: {
            'click .cancel': 'hideForm',
            'click .add'   : 'showForm',
            'submit form'  : 'createCard',
            'keyup input'  : 'cancel'
        },

        /**
         *
         * @param {Dingbat.Model.Card} card
         */
        addCard: function(card) {
            // create card-view
            var view = new Dingbat.View.Card({model: card});

            // set view to model
            card.view = view;

            // render view
            view.render().$el.appendTo(this.$('.list')).hide().fadeIn();
        },

        cancel: function(event) {
            if (event.which == 27) {
                this.hideForm();
            }
        },

        createCard: function() {
            // create model
            var model = new Dingbat.Model.Card();

            // add model to collection after saving
            this.listenToOnce(model, 'sync', this.addCard);

            // set properties and save
            model.set('name', this.$('input').val());
            model.save();

            // hide form
            this.hideForm();

            return false;
        },

        getActiveCard: function() {
            return this.activeCard;
        },

        hideForm: function() {
            this.$('input').val('');
            this.$('input').animate({
                'opacity':0
            }, 200, 'swing', function() {
                $(this).css('visibility', 'hidden');
            });
            this.$('.cancel').hide();
            this.$('.add').fadeIn();
        },

        /**
         *
         * @param {Dingbat.View.Card} card
         */
        setActiveCard: function(card) {
            if (this.activeCard != null) {
                if (card.model.id == this.activeCard.model.id) {
                    return false;
                }

                this.activeCard.deactivate();
            }

            this.activeCard = card;
            this.trigger('change:active', card);
        },

        setListener: function() {
            this.listenTo(Dingbat.App.Cards, 'add', this.addCard);
        },

        showForm: function() {
            this.$('input').css('visibility', 'visible');
            this.$('input').animate({
                'opacity':1
            }, 200).focus();
            this.$('.add').hide();
            this.$('.cancel').fadeIn();
        }

    });

})

<commit_msg>added card to collection after saving<commit_after>$(function() {
    'use strict';

    Dingbat.View.Navigation = Backbone.Layout.extend({

        template: '#navigation-template',

        activeCard: null,

        events: {
            'click .cancel': 'hideForm',
            'click .add'   : 'showForm',
            'submit form'  : 'createCard',
            'keyup input'  : 'cancel'
        },

        /**
         * @param {Dingbat.Model.Card} model
         */
        addCard: function(model) {
            // create card-view
            var view = new Dingbat.View.Card({model: model});

            // set view to model
            model.view = view;

            // render view
            view.render().$el.appendTo(this.$('.list')).hide().fadeIn();
        },

        /**
         * @param {Dingbat.Model.Card} model
         */
        addCardToCollection: function(model) {
            Dingbat.App.Cards.add(model);

            // "redirect" to card
            Dingbat.App.Router.navigate('card/' + model.id, {trigger: true});
        },

        /**
         * @param {Event} event
         */
        cancel: function(event) {
            if (event.which == 27) {
                this.hideForm();
            }
        },

        createCard: function() {
            // create model
            var model = new Dingbat.Model.Card();

            // add model to collection and layout after saving
            this.listenToOnce(model, 'sync', this.addCardToCollection);

            // set properties and save
            model.set('name', this.$('input').val());
            model.save();

            // hide form
            this.hideForm();

            return false;
        },

        getActiveCard: function() {
            return this.activeCard;
        },

        hideForm: function() {
            this.$('input').val('');
            this.$('input').animate({
                'opacity':0
            }, 200, 'swing', function() {
                $(this).css('visibility', 'hidden');
            });
            this.$('.cancel').hide();
            this.$('.add').fadeIn();
        },

        /**
         * @param {Dingbat.View.Card} view
         */
        setActiveCard: function(view) {
            if (this.activeCard != null) {
                if (view.model.id == this.activeCard.model.id) {
                    return false;
                }

                this.activeCard.deactivate();
            }

            this.activeCard = view;
            this.trigger('change:active', view);
        },

        setListener: function() {
            this.listenTo(Dingbat.App.Cards, 'add', this.addCard);
        },

        showForm: function() {
            this.$('input').css('visibility', 'visible');
            this.$('input').animate({
                'opacity':1
            }, 200).focus();
            this.$('.add').hide();
            this.$('.cancel').fadeIn();
        }

    });

})

<|endoftext|>


<commit_before>"use strict";

describe('configurator', function() {
  var mock1 = {};
  var mock2 = {};
  beforeEach(module('arethusa', function($provide) {
    $provide.value('x', mock1);
    $provide.value('y', mock2);
  }));

  describe('this.configurationFor', function() {
    it('provides the configuration for a given plugin', inject(function(configurator) {
      // the configuration is usually provide from an external route
      configurator.configuration = { "text" : 'conf' };
      expect(configurator.configurationFor('text')).toEqual('conf');
    }));
  });

  describe('this.getService', function() {
    it('retrieves an angular instance by name', inject(function(configurator) {
      expect(configurator.getService('x')).toEqual(mock1);
    }));
  });

  describe('this.getServices', function() {
    it('retrieves an array of angular instance by name', inject(function(configurator) {
      var names = ['x', 'y'];
      var services = configurator.getServices(names);
      expect(services.length).toEqual(2);
      expect(services).toContain(mock1);
      expect(services).toContain(mock2);
    }));

    it('returns an empty array when no service names are given', inject(function(configurator) {
      var names = undefined;
      var services = configurator.getServices(names);
      expect(services).toEqual([]);
    }));
  });
});
<commit_msg>Ok, don't declare a variable as undefined<commit_after>"use strict";

describe('configurator', function() {
  var mock1 = {};
  var mock2 = {};
  beforeEach(module('arethusa', function($provide) {
    $provide.value('x', mock1);
    $provide.value('y', mock2);
  }));

  describe('this.configurationFor', function() {
    it('provides the configuration for a given plugin', inject(function(configurator) {
      // the configuration is usually provide from an external route
      configurator.configuration = { "text" : 'conf' };
      expect(configurator.configurationFor('text')).toEqual('conf');
    }));
  });

  describe('this.getService', function() {
    it('retrieves an angular instance by name', inject(function(configurator) {
      expect(configurator.getService('x')).toEqual(mock1);
    }));
  });

  describe('this.getServices', function() {
    it('retrieves an array of angular instance by name', inject(function(configurator) {
      var names = ['x', 'y'];
      var services = configurator.getServices(names);
      expect(services.length).toEqual(2);
      expect(services).toContain(mock1);
      expect(services).toContain(mock2);
    }));

    it('returns an empty array when no service names are given', inject(function(configurator) {
      var services = configurator.getServices(undefined);
      expect(services).toEqual([]);
    }));
  });
});
<|endoftext|>


<commit_before>import CouchClient from "../CouchClient";
import { FEED_LIMIT_TO_DELETE_IN_QUERY } from "../util/Constants";
import DateUtil from "../util/DateUtil";
import R from "ramda"; //eslint-disable-line id-length
import Logger from "../logging/Logger";
import { FEED_DOCTYPE } from "./../util/Constants";

export default class DeleteSourceHandler {
    constructor(accessToken) {
        this.couchClient = CouchClient.instance(accessToken);
    }
    static instance(accessToken) {
        return new DeleteSourceHandler(accessToken);
    }

    static logger() {
        return Logger.instance("DeleteSourceHandler");
    }

    async deleteSources(sources = []) {
        let sourceDocuments = [];
        if (sources.length) {
            sourceDocuments = await this.fetchSourceDocuments(sources);
            await this.deleteFeeds(sourceDocuments);
        }
        return { "ok": true };
    }

    async deleteHashTags() {
        let sourceDocuments = await this._fetchHashtagSources();
        await this.deleteFeeds(sourceDocuments);
    }

    async deleteFeeds(sources) {
        let sourceIds = sources.map(source => source._id);
        const feedDocuments = await this._getFeedsFromSources(sourceIds);
        let toBeDeleted = [];
        let toBeMarked = [];
        let currentFeedDoc = {};
        let updatedCollectionFeedDocs = [];

        feedDocuments.forEach((feedDoc) => {
            if(feedDoc.docType === FEED_DOCTYPE) {
                currentFeedDoc = feedDoc;

                if(feedDoc.bookmark) {
                    toBeMarked.push(feedDoc);
                } else {
                    toBeDeleted.push(feedDoc);
                }
            } else {
                const { _id, collectionId } = feedDoc;
                const { title, description, link,
                tags, sourceType, pubDate } = currentFeedDoc;
                const sourceDeleted = true;
                const selectText = true;

                const updatedCollectionFeedDoc = { _id, collectionId, title,
                    description, link, tags, sourceType,
                    pubDate, selectText, sourceDeleted };

                updatedCollectionFeedDocs.push(updatedCollectionFeedDoc);
            }
        });

        let docsToBeDeleted = toBeDeleted.concat(sources);
        await this.couchClient.deleteBulkDocuments(docsToBeDeleted);
        try {
            const markedFeeds = await this.markAsSourceDeleted(toBeMarked);
            updatedCollectionFeedDocs.push(...markedFeeds);
            await this.couchClient.saveBulkDocuments({ "docs": updatedCollectionFeedDocs });
        } catch(err) {
            DeleteSourceHandler.logger().error(`Error making feeds as source deleted, Error:: ${JSON.stringify(err)}`);
        }
    }

    async _getFeedsFromSources(sources) {
        const selector = {
            "selector": {
                "_id": {
                    "$gt": null
                },
                "docType": {
                    "$in": ["feed", "collectionFeed"]
                },
                "sourceId": {
                    "$in": sources
                }
            },
            "skip": 0,
            "limit": FEED_LIMIT_TO_DELETE_IN_QUERY
        };

        return await this._findDocuments(selector);
    }

    async _fetchHashtagSources() {
        const selector = {
            "selector": {
                "docType": {
                    "$eq": "source"
                },
                "hashtag": {
                    "$eq": true
                }
            },
            "skip": 0
        };

        return await this._findDocuments(selector);
    }

    async fetchSourceDocuments(sources) {
        const selector = {
            "selector": {
                "docType": {
                    "$eq": "source"
                },
                "_id": {
                    "$in": sources
                }
            },
            "skip": 0
        };
        return await this._findDocuments(selector);
    }

    async _findDocuments(selector, docs = []) {
        const docsInReq = await this.couchClient.findDocuments(selector);
        if(docsInReq.docs.length === FEED_LIMIT_TO_DELETE_IN_QUERY) {
            const updatedSelector = Object.assign({}, selector, { "skip": selector.skip + FEED_LIMIT_TO_DELETE_IN_QUERY });
            return await this._findDocuments(updatedSelector, docs.concat(docsInReq.docs));
        }
        return docs.concat(docsInReq.docs);
    }

    async markAsSourceDeleted(feeds) {
        return R.map(feed => {
            feed.sourceDeleted = true;
            return feed;
        })(feeds);
    }

    async deleteOldFeeds() {
        let selector = null, docsObject = {}, docs = [], MONTH_DAYS = 30;
        let currentDate = new Date();
        currentDate.setDate(currentDate.getDate() - MONTH_DAYS);
        let deleteDate = DateUtil.getUTCDateAndTime(currentDate);
        do { //eslint-disable-line no-loops/no-loops
            selector = {
                "selector": {
                    "pubDate": {
                        "$lt": deleteDate
                    }
                }
            };
            docsObject = await this.couchClient.findDocuments(selector);
            docs = docsObject.docs;

            if(docs.length) {
                await this.couchClient.deleteBulkDocuments(docs);
            }
        }
        while (docs.length);
    }
}
<commit_msg>#112 - Added docType and _rev to collectionFeed document<commit_after>import CouchClient from "../CouchClient";
import { FEED_LIMIT_TO_DELETE_IN_QUERY } from "../util/Constants";
import DateUtil from "../util/DateUtil";
import R from "ramda"; //eslint-disable-line id-length
import Logger from "../logging/Logger";
import { FEED_DOCTYPE } from "./../util/Constants";

export default class DeleteSourceHandler {
    constructor(accessToken) {
        this.couchClient = CouchClient.instance(accessToken);
    }
    static instance(accessToken) {
        return new DeleteSourceHandler(accessToken);
    }

    static logger() {
        return Logger.instance("DeleteSourceHandler");
    }

    async deleteSources(sources = []) {
        let sourceDocuments = [];
        if (sources.length) {
            sourceDocuments = await this.fetchSourceDocuments(sources);
            await this.deleteFeeds(sourceDocuments);
        }
        return { "ok": true };
    }

    async deleteHashTags() {
        let sourceDocuments = await this._fetchHashtagSources();
        await this.deleteFeeds(sourceDocuments);
    }

    async deleteFeeds(sources) {
        let sourceIds = sources.map(source => source._id);
        const feedDocuments = await this._getFeedsFromSources(sourceIds);
        let toBeDeleted = [];
        let toBeMarked = [];
        let currentFeedDoc = {};
        let updatedCollectionFeedDocs = [];

        feedDocuments.forEach((feedDoc) => {
            if(feedDoc.docType === FEED_DOCTYPE) {
                currentFeedDoc = feedDoc;

                if(feedDoc.bookmark) {
                    toBeMarked.push(feedDoc);
                } else {
                    toBeDeleted.push(feedDoc);
                }
            } else {
                const { _id, collectionId, docType, _rev } = feedDoc;
                const { title, description, link,
                tags, sourceType, pubDate } = currentFeedDoc;
                const sourceDeleted = true;
                const selectText = true;

                const updatedCollectionFeedDoc = { _id, _rev, collectionId, title,
                    description, link, tags, sourceType,
                    pubDate, selectText, sourceDeleted, docType };
                updatedCollectionFeedDocs.push(updatedCollectionFeedDoc);
            }
        });

        let docsToBeDeleted = toBeDeleted.concat(sources);
        await this.couchClient.deleteBulkDocuments(docsToBeDeleted);
        try {
            const markedFeeds = await this.markAsSourceDeleted(toBeMarked);
            updatedCollectionFeedDocs.push(...markedFeeds);
            await this.couchClient.saveBulkDocuments({ "docs": updatedCollectionFeedDocs });
        } catch(err) {
            DeleteSourceHandler.logger().error(`Error making feeds as source deleted, Error:: ${JSON.stringify(err)}`);
        }
    }

    async _getFeedsFromSources(sources) {
        const selector = {
            "selector": {
                "_id": {
                    "$gt": null
                },
                "docType": {
                    "$in": ["feed", "collectionFeed"]
                },
                "sourceId": {
                    "$in": sources
                }
            },
            "skip": 0,
            "limit": FEED_LIMIT_TO_DELETE_IN_QUERY
        };

        return await this._findDocuments(selector);
    }

    async _fetchHashtagSources() {
        const selector = {
            "selector": {
                "docType": {
                    "$eq": "source"
                },
                "hashtag": {
                    "$eq": true
                }
            },
            "skip": 0
        };

        return await this._findDocuments(selector);
    }

    async fetchSourceDocuments(sources) {
        const selector = {
            "selector": {
                "docType": {
                    "$eq": "source"
                },
                "_id": {
                    "$in": sources
                }
            },
            "skip": 0
        };
        return await this._findDocuments(selector);
    }

    async _findDocuments(selector, docs = []) {
        const docsInReq = await this.couchClient.findDocuments(selector);
        if(docsInReq.docs.length === FEED_LIMIT_TO_DELETE_IN_QUERY) {
            const updatedSelector = Object.assign({}, selector, { "skip": selector.skip + FEED_LIMIT_TO_DELETE_IN_QUERY });
            return await this._findDocuments(updatedSelector, docs.concat(docsInReq.docs));
        }
        return docs.concat(docsInReq.docs);
    }

    async markAsSourceDeleted(feeds) {
        return R.map(feed => {
            feed.sourceDeleted = true;
            return feed;
        })(feeds);
    }

    async deleteOldFeeds() {
        let selector = null, docsObject = {}, docs = [], MONTH_DAYS = 30;
        let currentDate = new Date();
        currentDate.setDate(currentDate.getDate() - MONTH_DAYS);
        let deleteDate = DateUtil.getUTCDateAndTime(currentDate);
        do { //eslint-disable-line no-loops/no-loops
            selector = {
                "selector": {
                    "pubDate": {
                        "$lt": deleteDate
                    }
                }
            };
            docsObject = await this.couchClient.findDocuments(selector);
            docs = docsObject.docs;

            if(docs.length) {
                await this.couchClient.deleteBulkDocuments(docs);
            }
        }
        while (docs.length);
    }
}
<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const chai = require('chai');
const karma = require('karma');

const expect = chai.expect;
const karmaCoverage = require('../src/reporter');

function createServer(config) {
  config = config || {};
  return new karma.Server(Object.assign({
    configFile: path.join(__dirname, '/karma.conf.js'),
    plugins: [
      'karma-mocha',
      'karma-phantomjs-launcher',
      'karma-webpack',
      'karma-sourcemap-loader',
      karmaCoverage
    ]
  }, config), () => {});
}

describe('karma-coverage-istanbul-reporter', () => {
  it('should generate a remapped coverage report', done => {
    const server = createServer();
    server.start();
    server.on('run_complete', () => {
      setTimeout(() => { // hacky workaround to make sure the file has been written
        const summary = JSON.parse(fs.readFileSync(path.join(__dirname, '/fixtures/outputs/coverage-summary.json')));
        expect(summary.total).to.deep.equal({
          lines: {
            total: 6,
            covered: 5,
            skipped: 0,
            pct: 83.33
          },
          statements: {
            total: 7,
            covered: 6,
            skipped: 0,
            pct: 85.71
          },
          functions: {
            total: 3,
            covered: 2,
            skipped: 0,
            pct: 66.67
          },
          branches: {
            total: 0,
            covered: 0,
            skipped: 0,
            pct: 100
          }
        });
        done();
      }, 300);
    });
  });

  it('should fix webpack loader source paths', done => {
    const server = createServer({
      coverageIstanbulReporter: {
        reports: ['json-summary'],
        dir: './test/fixtures/outputs',
        fixWebpackSourcePaths: true
      }
    });
    server.start();
    server.on('run_complete', () => {
      setTimeout(() => { // hacky workaround to make sure the file has been written
        const summary = JSON.parse(fs.readFileSync(path.join(__dirname, '/fixtures/outputs/coverage-summary.json')));
        const files = Object.keys(summary);
        files.forEach(file => {
          expect(file).not.to.contain('tslint-loader');
        });
        done();
      }, 300);
    });
  });
});
<commit_msg>Hack around eslint warning<commit_after>const fs = require('fs');
const path = require('path');
const chai = require('chai');
const karma = require('karma');

const expect = chai.expect;
const karmaCoverage = require('../src/reporter');

function createServer(config) {
  config = config || {};
  return new karma.Server(Object.assign({
    configFile: path.join(__dirname, '/karma.conf.js'),
    plugins: [
      'karma-mocha',
      'karma-phantomjs-launcher',
      'karma-webpack',
      'karma-sourcemap-loader',
      karmaCoverage
    ]
  }, config), () => {});
}

describe('karma-coverage-istanbul-reporter', () => {
  it('should generate a remapped coverage report', done => {
    const server = createServer();
    server.start();
    server.on('run_complete', () => {
      setTimeout(() => { // hacky workaround to make sure the file has been written
        const summary = JSON.parse(fs.readFileSync(path.join(__dirname, '/fixtures/outputs/coverage-summary.json')));
        expect(summary.total).to.deep.equal({
          lines: {
            total: 6,
            covered: 5,
            skipped: 0,
            pct: 83.33
          },
          statements: {
            total: 7,
            covered: 6,
            skipped: 0,
            pct: 85.71
          },
          functions: {
            total: 3,
            covered: 2,
            skipped: 0,
            pct: 66.67
          },
          branches: {
            total: 0,
            covered: 0,
            skipped: 0,
            pct: 100
          }
        });
        done();
      }, 300);
    });
  });

  it('should fix webpack loader source paths', done => {
    const server = createServer({
      coverageIstanbulReporter: {
        reports: ['json-summary'],
        dir: './test/fixtures/outputs',
        fixWebpackSourcePaths: true
      }
    });
    server.start();
    server.on('run_complete', () => {
      setTimeout(() => { // hacky workaround to make sure the file has been written
        const summary = JSON.parse(fs.readFileSync(path.join(__dirname, '/fixtures/outputs/coverage-summary.json')));
        const files = Object.keys(summary);
        files.forEach(file => { // eslint-disable-line
          expect(file).not.to.contain('tslint-loader');
        });
        done();
      }, 300);
    });
  });
});
<|endoftext|>


<commit_before>const temp = require('temp').track()
const fs = require('fs-extra')
const path = require('path')

const {CompositeDisposable} = require('event-kit')
const {watchPath, stopAllWatchers} = require('../lib')

process.on('unhandledRejection', r => console.log(r))

describe('exported functions', function () {
  let subs

  beforeEach(function () {
    subs = new CompositeDisposable()
  })

  afterEach(async function () {
    subs.dispose()
    await stopAllWatchers()
    await tempCleanup()
  })

  function tempMkdir (...args) {
    return new Promise((resolve, reject) => {
      temp.mkdir(...args, (err, dirPath) => {
        if (err) {
          reject(err)
        } else {
          resolve(dirPath)
        }
      })
    })
  }

  function tempCleanup () {
    return new Promise((resolve, reject) => {
      temp.cleanup(err => {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  function waitForChanges (watcher, ...fileNames) {
    const waiting = new Set(fileNames)
    let fired = false
    const relevantEvents = []

    return new Promise(resolve => {
      const sub = watcher.onDidChange(events => {
        for (const event of events) {
          if (waiting.delete(event.path)) {
            relevantEvents.push(event)
          }
        }

        if (!fired && waiting.size === 0) {
          fired = true
          resolve(relevantEvents)
          sub.dispose()
        }
      })
    })
  }

  describe('watchPath()', function () {
    it('resolves the returned promise when the watcher begins listening', async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const watcher = await watchPath(rootDir, {}, () => {})
      assert.strictEqual(watcher.constructor.name, 'PathWatcher')
    })

    it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const watcher0 = await watchPath(rootDir, {}, () => {})
      const watcher1 = await watchPath(rootDir, {}, () => {})

      assert.strictEqual(watcher0.native, watcher1.native)
    })

    it("reuses existing native watchers even while they're still starting", async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const [watcher0, watcher1] = await Promise.all([
        watchPath(rootDir, {}, () => {}),
        watchPath(rootDir, {}, () => {})
      ])
      assert.strictEqual(watcher0.native, watcher1.native)
    })

    it("doesn't attach new watchers to a native watcher that's stopping", async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const watcher0 = await watchPath(rootDir, {}, () => {})
      const native0 = watcher0.native

      watcher0.dispose()
      const watcher1 = await watchPath(rootDir, {}, () => {})

      assert.notStrictEqual(watcher1.native, native0)
    })

    it('reuses an existing native watcher on a parent directory and filters events', async function () {
      const rootDir = await tempMkdir('atom-watcher-test-').then(fs.realpath)
      const rootFile = path.join(rootDir, 'rootfile.txt')
      const subDir = path.join(rootDir, 'subdir')
      const subFile = path.join(subDir, 'subfile.txt')

      await fs.mkdir(subDir)

      // Keep the watchers alive with an undisposed subscription
      const rootWatcher = await watchPath(rootDir, {}, () => {})
      const childWatcher = await watchPath(subDir, {}, () => {})

      assert.strictEqual(rootWatcher.native, childWatcher.native)
      assert.isTrue(rootWatcher.native.isRunning())

      const firstChanges = Promise.all([
        waitForChanges(rootWatcher, subFile),
        waitForChanges(childWatcher, subFile)
      ])
      await fs.writeFile(subFile, 'subfile\n', {encoding: 'utf8'})
      await firstChanges

      const nextRootEvent = waitForChanges(rootWatcher, rootFile)
      await fs.writeFile(rootFile, 'rootfile\n', {encoding: 'utf8'})
      await nextRootEvent
    })

    it('adopts existing child watchers and filters events appropriately to them', async function () {
      const parentDir = await tempMkdir('atom-watcher-test-').then(fs.realpath)

      // Create the directory tree
      const rootFile = path.join(parentDir, 'rootfile.txt')
      const subDir0 = path.join(parentDir, 'subdir0')
      const subFile0 = path.join(subDir0, 'subfile0.txt')
      const subDir1 = path.join(parentDir, 'subdir1')
      const subFile1 = path.join(subDir1, 'subfile1.txt')

      await fs.mkdir(subDir0)
      await fs.mkdir(subDir1)
      await Promise.all([
        fs.writeFile(rootFile, 'rootfile\n', {encoding: 'utf8'}),
        fs.writeFile(subFile0, 'subfile 0\n', {encoding: 'utf8'}),
        fs.writeFile(subFile1, 'subfile 1\n', {encoding: 'utf8'})
      ])

      // Begin the child watchers and keep them alive
      const subWatcher0 = await watchPath(subDir0, {}, () => {})
      const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0)

      const subWatcher1 = await watchPath(subDir1, {}, () => {})
      const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1)

      assert.notStrictEqual(subWatcher0.native, subWatcher1.native)

      // Create the parent watcher
      const parentWatcher = await watchPath(parentDir, {}, () => {})
      const parentWatcherChanges = waitForChanges(parentWatcher, rootFile, subFile0, subFile1)

      assert.strictEqual(subWatcher0.native, parentWatcher.native)
      assert.strictEqual(subWatcher1.native, parentWatcher.native)

      // Ensure events are filtered correctly
      await Promise.all([
        fs.appendFile(rootFile, 'change\n', {encoding: 'utf8'}),
        fs.appendFile(subFile0, 'change\n', {encoding: 'utf8'}),
        fs.appendFile(subFile1, 'change\n', {encoding: 'utf8'})
      ])

      await Promise.all([
        subWatcherChanges0,
        subWatcherChanges1,
        parentWatcherChanges
      ])
    })
  })
})
<commit_msg>Do temp cleanup manually<commit_after>const temp = require('temp')
const fs = require('fs-extra')
const path = require('path')

const {CompositeDisposable} = require('event-kit')
const {watchPath, stopAllWatchers, printWatchers} = require('../lib')

process.on('unhandledRejection', r => console.log(r))

let tempDirs = []

describe('exported functions', function () {
  let subs

  beforeEach(function () {
    subs = new CompositeDisposable()
  })

  afterEach(async function () {
    subs.dispose()

    await stopAllWatchers()

    await Promise.all(
      tempDirs.map(tempDir => {
        return fs.remove(tempDir, {maxBusyTries: 1})
          .catch(err => { console.warn('Unable to delete fixture directory', err)})
      })
    )
    tempDirs = []
  })

  function tempMkdir (...args) {
    return new Promise((resolve, reject) => {
      temp.mkdir(...args, (err, dirPath) => {
        if (err) {
          reject(err)
        } else {
          tempDirs.push(dirPath)
          resolve(dirPath)
        }
      })
    })
  }

  function waitForChanges (watcher, ...fileNames) {
    const waiting = new Set(fileNames)
    let fired = false
    const relevantEvents = []

    return new Promise(resolve => {
      const sub = watcher.onDidChange(events => {
        for (const event of events) {
          if (waiting.delete(event.path)) {
            relevantEvents.push(event)
          }
        }

        if (!fired && waiting.size === 0) {
          fired = true
          resolve(relevantEvents)
          sub.dispose()
        }
      })
    })
  }

  describe('watchPath()', function () {
    it('resolves the returned promise when the watcher begins listening', async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const watcher = await watchPath(rootDir, {}, () => {})
      assert.strictEqual(watcher.constructor.name, 'PathWatcher')
    })

    it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const watcher0 = await watchPath(rootDir, {}, () => {})
      const watcher1 = await watchPath(rootDir, {}, () => {})

      assert.strictEqual(watcher0.native, watcher1.native)
    })

    it("reuses existing native watchers even while they're still starting", async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const [watcher0, watcher1] = await Promise.all([
        watchPath(rootDir, {}, () => {}),
        watchPath(rootDir, {}, () => {})
      ])
      assert.strictEqual(watcher0.native, watcher1.native)
    })

    it("doesn't attach new watchers to a native watcher that's stopping", async function () {
      const rootDir = await tempMkdir('atom-watcher-test-')

      const watcher0 = await watchPath(rootDir, {}, () => {})
      const native0 = watcher0.native

      watcher0.dispose()
      const watcher1 = await watchPath(rootDir, {}, () => {})

      assert.notStrictEqual(watcher1.native, native0)
    })

    it('reuses an existing native watcher on a parent directory and filters events', async function () {
      const rootDir = await tempMkdir('atom-watcher-test-').then(fs.realpath)
      const rootFile = path.join(rootDir, 'rootfile.txt')
      const subDir = path.join(rootDir, 'subdir')
      const subFile = path.join(subDir, 'subfile.txt')

      await fs.mkdir(subDir)

      // Keep the watchers alive with an undisposed subscription
      const rootWatcher = await watchPath(rootDir, {}, () => {})
      const childWatcher = await watchPath(subDir, {}, () => {})

      assert.strictEqual(rootWatcher.native, childWatcher.native)
      assert.isTrue(rootWatcher.native.isRunning())

      const firstChanges = Promise.all([
        waitForChanges(rootWatcher, subFile),
        waitForChanges(childWatcher, subFile)
      ])
      await fs.writeFile(subFile, 'subfile\n', {encoding: 'utf8'})
      await firstChanges

      const nextRootEvent = waitForChanges(rootWatcher, rootFile)
      await fs.writeFile(rootFile, 'rootfile\n', {encoding: 'utf8'})
      await nextRootEvent
    })

    it('adopts existing child watchers and filters events appropriately to them', async function () {
      const parentDir = await tempMkdir('atom-watcher-test-').then(fs.realpath)

      // Create the directory tree
      const rootFile = path.join(parentDir, 'rootfile.txt')
      const subDir0 = path.join(parentDir, 'subdir0')
      const subFile0 = path.join(subDir0, 'subfile0.txt')
      const subDir1 = path.join(parentDir, 'subdir1')
      const subFile1 = path.join(subDir1, 'subfile1.txt')

      await fs.mkdir(subDir0)
      await fs.mkdir(subDir1)
      await Promise.all([
        fs.writeFile(rootFile, 'rootfile\n', {encoding: 'utf8'}),
        fs.writeFile(subFile0, 'subfile 0\n', {encoding: 'utf8'}),
        fs.writeFile(subFile1, 'subfile 1\n', {encoding: 'utf8'})
      ])

      // Begin the child watchers and keep them alive
      const subWatcher0 = await watchPath(subDir0, {}, () => {})
      const subWatcherChanges0 = waitForChanges(subWatcher0, subFile0)

      const subWatcher1 = await watchPath(subDir1, {}, () => {})
      const subWatcherChanges1 = waitForChanges(subWatcher1, subFile1)

      assert.notStrictEqual(subWatcher0.native, subWatcher1.native)

      // Create the parent watcher
      const parentWatcher = await watchPath(parentDir, {}, () => {})
      const parentWatcherChanges = waitForChanges(parentWatcher, rootFile, subFile0, subFile1)

      assert.strictEqual(subWatcher0.native, parentWatcher.native)
      assert.strictEqual(subWatcher1.native, parentWatcher.native)

      // Ensure events are filtered correctly
      await Promise.all([
        fs.appendFile(rootFile, 'change\n', {encoding: 'utf8'}),
        fs.appendFile(subFile0, 'change\n', {encoding: 'utf8'}),
        fs.appendFile(subFile1, 'change\n', {encoding: 'utf8'})
      ])

      await Promise.all([
        subWatcherChanges0,
        subWatcherChanges1,
        parentWatcherChanges
      ])
    })
  })
})
<|endoftext|>


<commit_before>/*!
 * numeral.js
 * version : 1.5.1
 * author : Adam Draper
 * license : MIT
 * http://adamwdraper.github.com/Numeral-js/
 */
(function(){function a(a){this._value=a}function b(a,b,c,d){var e,f,g=Math.pow(10,b);return f=(c(a*g)/g).toFixed(b),d&&(e=new RegExp("0{1,"+d+"}$"),f=f.replace(e,"")),f}function c(a,b,c){var d;return d=b.indexOf("$")>-1?e(a,b,c):b.indexOf("%")>-1?f(a,b,c):b.indexOf(":")>-1?g(a,b):i(a._value,b,c)}function d(a,b){var c,d,e,f,g,i=b,j=["KB","MB","GB","TB","PB","EB","ZB","YB"],k=!1;if(b.indexOf(":")>-1)a._value=h(b);else if(b===o)a._value=0;else{for("."!==m[n].delimiters.decimal&&(b=b.replace(/\./g,"").replace(m[n].delimiters.decimal,".")),c=new RegExp("[^a-zA-Z]"+m[n].abbreviations.thousand+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),d=new RegExp("[^a-zA-Z]"+m[n].abbreviations.million+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),e=new RegExp("[^a-zA-Z]"+m[n].abbreviations.billion+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),f=new RegExp("[^a-zA-Z]"+m[n].abbreviations.trillion+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),g=0;g<=j.length&&!(k=b.indexOf(j[g])>-1?Math.pow(1024,g+1):!1);g++);a._value=(k?k:1)*(i.match(c)?Math.pow(10,3):1)*(i.match(d)?Math.pow(10,6):1)*(i.match(e)?Math.pow(10,9):1)*(i.match(f)?Math.pow(10,12):1)*(b.indexOf("%")>-1?.01:1)*((b.split("-").length+Math.min(b.split("(").length-1,b.split(")").length-1))%2?1:-1)*Number(b.replace(/[^0-9\.]+/g,"")),a._value=k?Math.ceil(a._value):a._value}return a._value}function e(a,b,c){var d,e=b.indexOf("$")<=1?!0:!1,f="";return b.indexOf(" $")>-1?(f=" ",b=b.replace(" $","")):b.indexOf("$ ")>-1?(f=" ",b=b.replace("$ ","")):b=b.replace("$",""),d=i(a._value,b,c),e?d.indexOf("(")>-1||d.indexOf("-")>-1?(d=d.split(""),d.splice(1,0,m[n].currency.symbol+f),d=d.join("")):d=m[n].currency.symbol+f+d:d.indexOf(")")>-1?(d=d.split(""),d.splice(-1,0,f+m[n].currency.symbol),d=d.join("")):d=d+f+m[n].currency.symbol,d}function f(a,b,c){var d,e="",f=100*a._value;return b.indexOf(" %")>-1?(e=" ",b=b.replace(" %","")):b=b.replace("%",""),d=i(f,b,c),d.indexOf(")")>-1?(d=d.split(""),d.splice(-1,0,e+"%"),d=d.join("")):d=d+e+"%",d}function g(a){var b=Math.floor(a._value/60/60),c=Math.floor((a._value-60*60*b)/60),d=Math.round(a._value-60*60*b-60*c);return b+":"+(10>c?"0"+c:c)+":"+(10>d?"0"+d:d)}function h(a){var b=a.split(":"),c=0;return 3===b.length?(c+=60*60*Number(b[0]),c+=60*Number(b[1]),c+=Number(b[2])):2===b.length&&(c+=60*Number(b[0]),c+=Number(b[1])),Number(c)}function i(a,c,d){var e,f,g,h,i,j,k=!1,l=!1,p=!1,q="",r="",s="",t=Math.abs(a),u=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],v="",w=!1;if(0===a&&null!==o)return o;if(c.indexOf("(")>-1?(k=!0,c=c.slice(1,-1)):c.indexOf("+")>-1&&(l=!0,c=c.replace(/\+/g,"")),c.indexOf("a")>-1&&(c.indexOf(" a")>-1?(q=" ",c=c.replace(" a","")):c=c.replace("a",""),t>=Math.pow(10,12)?(q+=m[n].abbreviations.trillion,a/=Math.pow(10,12)):t<Math.pow(10,12)&&t>=Math.pow(10,9)?(q+=m[n].abbreviations.billion,a/=Math.pow(10,9)):t<Math.pow(10,9)&&t>=Math.pow(10,6)?(q+=m[n].abbreviations.million,a/=Math.pow(10,6)):t<Math.pow(10,6)&&t>=Math.pow(10,3)&&(q+=m[n].abbreviations.thousand,a/=Math.pow(10,3))),c.indexOf("b")>-1)for(c.indexOf(" b")>-1?(r=" ",c=c.replace(" b","")):c=c.replace("b",""),g=0;g<=u.length;g++)if(e=Math.pow(1024,g),f=Math.pow(1024,g+1),a>=e&&f>a){r+=u[g],e>0&&(a/=e);break}return c.indexOf("o")>-1&&(c.indexOf(" o")>-1?(s=" ",c=c.replace(" o","")):c=c.replace("o",""),s+=m[n].ordinal(a)),c.indexOf("[.]")>-1&&(p=!0,c=c.replace("[.]",".")),h=a.toString().split(".")[0],i=c.split(".")[1],j=c.indexOf(","),i?(i.indexOf("[")>-1?(i=i.replace("]",""),i=i.split("["),v=b(a,i[0].length+i[1].length,d,i[1].length)):v=b(a,i.length,d),h=v.split(".")[0],v=v.split(".")[1].length?m[n].delimiters.decimal+v.split(".")[1]:"",p&&0===Number(v.slice(1))&&(v="")):h=b(a,null,d),h.indexOf("-")>-1&&(h=h.slice(1),w=!0),j>-1&&(h=h.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+m[n].delimiters.thousands)),0===c.indexOf(".")&&(h=""),(k&&w?"(":"")+(!k&&w?"-":"")+(!w&&l?"+":"")+h+v+(s?s:"")+(q?q:"")+(r?r:"")+(k&&w?")":"")}function j(a,b){m[a]=b}var k,l="1.5.1",m={},n="en",o=null,p="0,0",q="undefined"!=typeof module&&module.exports;k=function(b){return k.isNumeral(b)?b=b.value():0===b||"undefined"==typeof b?b=0:Number(b)||(b=k.fn.unformat(b)),new a(Number(b))},k.version=l,k.isNumeral=function(b){return b instanceof a},k.language=function(a,b){if(!a)return n;if(a&&!b){if(!m[a])throw new Error("Unknown language : "+a);n=a}return(b||!m[a])&&j(a,b),k},k.languageData=function(a){if(!a)return m[n];if(!m[a])throw new Error("Unknown language : "+a);return m[a]},k.language("en",{delimiters:{thousands:",",decimal:"."},abbreviations:{thousand:"k",million:"m",billion:"b",trillion:"t"},ordinal:function(a){var b=a%10;return 1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th"},currency:{symbol:"$"}}),k.zeroFormat=function(a){o="string"==typeof a?a:null},k.defaultFormat=function(a){p="string"==typeof a?a:"0.0"},k.fn=a.prototype={clone:function(){return k(this)},format:function(a,b){return c(this,a?a:p,void 0!==b?b:Math.round)},unformat:function(a){return d(this,a?a:p)},value:function(){return this._value},valueOf:function(){return this._value},set:function(a){return this._value=Number(a),this},add:function(a){return this._value=this._value+Number(a),this},subtract:function(a){return this._value=this._value-Number(a),this},multiply:function(a){return this._value=this._value*Number(a),this},divide:function(a){return this._value=this._value/Number(a),this},difference:function(a){var b=this._value-Number(a);return 0>b&&(b=-b),b}},q&&(module.exports=k),"undefined"==typeof ender&&(this.numeral=k),"function"==typeof define&&define.amd&&define([],function(){return k})}).call(this);<commit_msg>build<commit_after>/*!
 * numeral.js
 * version : 1.5.1
 * author : Adam Draper
 * license : MIT
 * http://adamwdraper.github.com/Numeral-js/
 */
(function(){function a(a){this._value=a}function b(a,b,c,d){var e,f,g=Math.pow(10,b);return f=(c(a*g)/g).toFixed(b),d&&(e=new RegExp("0{1,"+d+"}$"),f=f.replace(e,"")),f}function c(a,b,c){var d;return d=b.indexOf("$")>-1?e(a,b,c):b.indexOf("%")>-1?f(a,b,c):b.indexOf(":")>-1?g(a,b):i(a._value,b,c)}function d(a,b){var c,d,e,f,g,i=b,j=["KB","MB","GB","TB","PB","EB","ZB","YB"],k=!1;if(b.indexOf(":")>-1)a._value=h(b);else if(b===o)a._value=0;else{for("."!==m[n].delimiters.decimal&&(b=b.replace(/\./g,"").replace(m[n].delimiters.decimal,".")),c=new RegExp("[^a-zA-Z]"+m[n].abbreviations.thousand+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),d=new RegExp("[^a-zA-Z]"+m[n].abbreviations.million+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),e=new RegExp("[^a-zA-Z]"+m[n].abbreviations.billion+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),f=new RegExp("[^a-zA-Z]"+m[n].abbreviations.trillion+"(?:\\)|(\\"+m[n].currency.symbol+")?(?:\\))?)?$"),g=0;g<=j.length&&!(k=b.indexOf(j[g])>-1?Math.pow(1024,g+1):!1);g++);a._value=(k?k:1)*(i.match(c)?Math.pow(10,3):1)*(i.match(d)?Math.pow(10,6):1)*(i.match(e)?Math.pow(10,9):1)*(i.match(f)?Math.pow(10,12):1)*(b.indexOf("%")>-1?.01:1)*((b.split("-").length+Math.min(b.split("(").length-1,b.split(")").length-1))%2?1:-1)*Number(b.replace(/[^0-9\.]+/g,"")),a._value=k?Math.ceil(a._value):a._value}return a._value}function e(a,b,c){var d,e=b.indexOf("$")<=1?!0:!1,f="";return b.indexOf(" $")>-1?(f=" ",b=b.replace(" $","")):b.indexOf("$ ")>-1?(f=" ",b=b.replace("$ ","")):b=b.replace("$",""),d=i(a._value,b,c),e?d.indexOf("(")>-1||d.indexOf("-")>-1?(d=d.split(""),d.splice(1,0,m[n].currency.symbol+f),d=d.join("")):d=m[n].currency.symbol+f+d:d.indexOf(")")>-1?(d=d.split(""),d.splice(-1,0,f+m[n].currency.symbol),d=d.join("")):d=d+f+m[n].currency.symbol,d}function f(a,b,c){var d,e="",f=100*a._value;return b.indexOf(" %")>-1?(e=" ",b=b.replace(" %","")):b=b.replace("%",""),d=i(f,b,c),d.indexOf(")")>-1?(d=d.split(""),d.splice(-1,0,e+"%"),d=d.join("")):d=d+e+"%",d}function g(a){var b=Math.floor(a._value/60/60),c=Math.floor((a._value-60*60*b)/60),d=Math.round(a._value-60*60*b-60*c);return b+":"+(10>c?"0"+c:c)+":"+(10>d?"0"+d:d)}function h(a){var b=a.split(":"),c=0;return 3===b.length?(c+=60*60*Number(b[0]),c+=60*Number(b[1]),c+=Number(b[2])):2===b.length&&(c+=60*Number(b[0]),c+=Number(b[1])),Number(c)}function i(a,c,d){var e,f,g,h,i,j,k=!1,l=!1,p=!1,q="",r="",s="",t=Math.abs(a),u=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],v="",w=!1;if(0===a&&null!==o)return o;if(c.indexOf("(")>-1?(k=!0,c=c.slice(1,-1)):c.indexOf("+")>-1&&(l=!0,c=c.replace(/\+/g,"")),c.indexOf("a")>-1&&(c.indexOf(" a")>-1?(q=" ",c=c.replace(" a","")):c=c.replace("a",""),t>=Math.pow(10,12)?(q+=m[n].abbreviations.trillion,a/=Math.pow(10,12)):t<Math.pow(10,12)&&t>=Math.pow(10,9)?(q+=m[n].abbreviations.billion,a/=Math.pow(10,9)):t<Math.pow(10,9)&&t>=Math.pow(10,6)?(q+=m[n].abbreviations.million,a/=Math.pow(10,6)):t<Math.pow(10,6)&&t>=Math.pow(10,3)&&(q+=m[n].abbreviations.thousand,a/=Math.pow(10,3))),c.indexOf("b")>-1)for(c.indexOf(" b")>-1?(r=" ",c=c.replace(" b","")):c=c.replace("b",""),g=0;g<=u.length;g++)if(e=Math.pow(1024,g),f=Math.pow(1024,g+1),a>=e&&f>a){r+=u[g],e>0&&(a/=e);break}return c.indexOf("o")>-1&&(c.indexOf(" o")>-1?(s=" ",c=c.replace(" o","")):c=c.replace("o",""),s+=m[n].ordinal(a)),c.indexOf("[.]")>-1&&(p=!0,c=c.replace("[.]",".")),h=a.toString().split(".")[0],i=c.split(".")[1],j=c.indexOf(","),i?(i.indexOf("[")>-1?(i=i.replace("]",""),i=i.split("["),v=b(a,i[0].length+i[1].length,d,i[1].length)):v=b(a,i.length,d),h=v.split(".")[0],v=v.split(".")[1].length?m[n].delimiters.decimal+v.split(".")[1]:"",p&&0===Number(v.slice(1))&&(v="")):h=b(a,null,d),h.indexOf("-")>-1&&(h=h.slice(1),w=!0),j>-1&&(h=h.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+m[n].delimiters.thousands)),0===c.indexOf(".")&&(h=""),(k&&w?"(":"")+(!k&&w?"-":"")+(!w&&l?"+":"")+h+v+(s?s:"")+(q?q:"")+(r?r:"")+(k&&w?")":"")}function j(a,b){m[a]=b}var k,l="1.5.1",m={},n="en",o=null,p="0,0",q="undefined"!=typeof module&&module.exports;k=function(b){return k.isNumeral(b)?b=b.value():0===b||"undefined"==typeof b?b=0:Number(b)||(b=k.fn.unformat(b)),new a(Number(b))},k.version=l,k.isNumeral=function(b){return b instanceof a},k.language=function(a,b){if(!a)return n;if(a&&!b){if(!m[a])throw new Error("Unknown language : "+a);n=a}return(b||!m[a])&&j(a,b),k},k.languageData=function(a){if(!a)return m[n];if(!m[a])throw new Error("Unknown language : "+a);return m[a]},k.language("en",{delimiters:{thousands:",",decimal:"."},abbreviations:{thousand:"k",million:"m",billion:"b",trillion:"t"},ordinal:function(a){var b=a%10;return 1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th"},currency:{symbol:"$"}}),k.zeroFormat=function(a){o="string"==typeof a?a:null},k.defaultFormat=function(a){p="string"==typeof a?a:"0.0"},k.fn=a.prototype={clone:function(){return k(this)},format:function(a,b){return c(this,a?a:p,void 0!==b?b:Math.round)},unformat:function(a){return"[object Number]"===Object.prototype.toString.call(a)?a:d(this,a?a:p)},value:function(){return this._value},valueOf:function(){return this._value},set:function(a){return this._value=Number(a),this},add:function(a){return this._value=this._value+Number(a),this},subtract:function(a){return this._value=this._value-Number(a),this},multiply:function(a){return this._value=this._value*Number(a),this},divide:function(a){return this._value=this._value/Number(a),this},difference:function(a){var b=this._value-Number(a);return 0>b&&(b=-b),b}},q&&(module.exports=k),"undefined"==typeof ender&&(this.numeral=k),"function"==typeof define&&define.amd&&define([],function(){return k})}).call(this);<|endoftext|>


<commit_before>import { FloatType, RGBAFormat } from '../../constants.js';
import { DataArrayTexture } from '../../textures/DataArrayTexture.js';
import { Vector4 } from '../../math/Vector4.js';
import { Vector2 } from '../../math/Vector2.js';

function numericalSort( a, b ) {

	return a[ 0 ] - b[ 0 ];

}

function absNumericalSort( a, b ) {

	return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );

}

function denormalize( morph, attribute ) {

	let denominator = 1;
	const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array;

	if ( array instanceof Int8Array ) denominator = 127;
	else if ( array instanceof Int16Array ) denominator = 32767;
	else if ( array instanceof Int32Array ) denominator = 2147483647;
	else console.error( 'THREE.WebGLMorphtargets: Unsupported morph attribute data type: ', array );

	morph.divideScalar( denominator );

}

function WebGLMorphtargets( gl, capabilities, textures ) {

	const influencesList = {};
	const morphInfluences = new Float32Array( 8 );
	const morphTextures = new WeakMap();
	const morph = new Vector4();

	const workInfluences = [];

	for ( let i = 0; i < 8; i ++ ) {

		workInfluences[ i ] = [ i, 0 ];

	}

	function update( object, geometry, material, program ) {

		const objectInfluences = object.morphTargetInfluences;

		if ( capabilities.isWebGL2 === true ) {

			// instead of using attributes, the WebGL 2 code path encodes morph targets
			// into an array of data textures. Each layer represents a single morph target.

			const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
			const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;

			let entry = morphTextures.get( geometry );

			if ( entry === undefined || entry.count !== morphTargetsCount ) {

				if ( entry !== undefined ) entry.texture.dispose();

				const hasMorphPosition = geometry.morphAttributes.position !== undefined;
				const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
				const hasMorphColors = geometry.morphAttributes.color !== undefined;

				const morphTargets = geometry.morphAttributes.position || [];
				const morphNormals = geometry.morphAttributes.normal || [];
				const morphColors = geometry.morphAttributes.color || [];

				let vertexDataCount = 0;

				if ( hasMorphPosition === true ) vertexDataCount = 1;
				if ( hasMorphNormals === true ) vertexDataCount = 2;
				if ( hasMorphColors === true ) vertexDataCount = 3;

				let width = geometry.attributes.position.count * vertexDataCount;
				let height = 1;

				if ( width > capabilities.maxTextureSize ) {

					height = Math.ceil( width / capabilities.maxTextureSize );
					width = capabilities.maxTextureSize;

				}

				const buffer = new Float32Array( width * height * 4 * morphTargetsCount );

				const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
				texture.format = RGBAFormat; // using RGBA since RGB might be emulated (and is thus slower)
				texture.type = FloatType;
				texture.needsUpdate = true;

				// fill buffer

				const vertexDataStride = vertexDataCount * 4;

				for ( let i = 0; i < morphTargetsCount; i ++ ) {

					const morphTarget = morphTargets[ i ];
					const morphNormal = morphNormals[ i ];
					const morphColor = morphColors[ i ];

					const offset = width * height * 4 * i;

					for ( let j = 0; j < morphTarget.count; j ++ ) {

						const stride = j * vertexDataStride;

						if ( hasMorphPosition === true ) {

							morph.fromBufferAttribute( morphTarget, j );

							if ( morphTarget.normalized === true ) denormalize( morph, morphTarget );

							buffer[ offset + stride + 0 ] = morph.x;
							buffer[ offset + stride + 1 ] = morph.y;
							buffer[ offset + stride + 2 ] = morph.z;
							buffer[ offset + stride + 3 ] = 0;

						}

						if ( hasMorphNormals === true ) {

							morph.fromBufferAttribute( morphNormal, j );

							if ( morphNormal.normalized === true ) denormalize( morph, morphNormal );

							buffer[ offset + stride + 4 ] = morph.x;
							buffer[ offset + stride + 5 ] = morph.y;
							buffer[ offset + stride + 6 ] = morph.z;
							buffer[ offset + stride + 7 ] = 0;

						}

						if ( hasMorphColors === true ) {

							morph.fromBufferAttribute( morphColor, j );

							if ( morphColor.normalized === true ) denormalize( morph, morphNormal );

							buffer[ offset + stride + 8 ] = morph.x;
							buffer[ offset + stride + 9 ] = morph.y;
							buffer[ offset + stride + 10 ] = morph.z;
							buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1;

						}

					}

				}

				entry = {
					count: morphTargetsCount,
					texture: texture,
					size: new Vector2( width, height )
				};

				morphTextures.set( geometry, entry );

				function disposeTexture() {

					texture.dispose();

					morphTextures.delete( geometry );

					geometry.removeEventListener( 'dispose', disposeTexture );

				}

				geometry.addEventListener( 'dispose', disposeTexture );

			}

			//

			let morphInfluencesSum = 0;

			for ( let i = 0; i < objectInfluences.length; i ++ ) {

				morphInfluencesSum += objectInfluences[ i ];

			}

			const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;

			program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
			program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );

			program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures );
			program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size );

		} else {

			// When object doesn't have morph target influences defined, we treat it as a 0-length array
			// This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences

			const length = objectInfluences === undefined ? 0 : objectInfluences.length;

			let influences = influencesList[ geometry.id ];

			if ( influences === undefined || influences.length !== length ) {

				// initialise list

				influences = [];

				for ( let i = 0; i < length; i ++ ) {

					influences[ i ] = [ i, 0 ];

				}

				influencesList[ geometry.id ] = influences;

			}

			// Collect influences

			for ( let i = 0; i < length; i ++ ) {

				const influence = influences[ i ];

				influence[ 0 ] = i;
				influence[ 1 ] = objectInfluences[ i ];

			}

			influences.sort( absNumericalSort );

			for ( let i = 0; i < 8; i ++ ) {

				if ( i < length && influences[ i ][ 1 ] ) {

					workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
					workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];

				} else {

					workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
					workInfluences[ i ][ 1 ] = 0;

				}

			}

			workInfluences.sort( numericalSort );

			const morphTargets = geometry.morphAttributes.position;
			const morphNormals = geometry.morphAttributes.normal;

			let morphInfluencesSum = 0;

			for ( let i = 0; i < 8; i ++ ) {

				const influence = workInfluences[ i ];
				const index = influence[ 0 ];
				const value = influence[ 1 ];

				if ( index !== Number.MAX_SAFE_INTEGER && value ) {

					if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {

						geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );

					}

					if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {

						geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );

					}

					morphInfluences[ i ] = value;
					morphInfluencesSum += value;

				} else {

					if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {

						geometry.deleteAttribute( 'morphTarget' + i );

					}

					if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {

						geometry.deleteAttribute( 'morphNormal' + i );

					}

					morphInfluences[ i ] = 0;

				}

			}

			// GLSL shader uses formula baseinfluence * base + sum(target * influence)
			// This allows us to switch between absolute morphs and relative morphs without changing shader code
			// When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence)
			const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;

			program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
			program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );

		}

	}

	return {

		update: update

	};

}

export { WebGLMorphtargets };
<commit_msg>Fix normalized morph color attribute (#23654)<commit_after>import { FloatType, RGBAFormat } from '../../constants.js';
import { DataArrayTexture } from '../../textures/DataArrayTexture.js';
import { Vector4 } from '../../math/Vector4.js';
import { Vector2 } from '../../math/Vector2.js';

function numericalSort( a, b ) {

	return a[ 0 ] - b[ 0 ];

}

function absNumericalSort( a, b ) {

	return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );

}

function denormalize( morph, attribute ) {

	let denominator = 1;
	const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array;

	if ( array instanceof Int8Array ) denominator = 127;
	else if ( array instanceof Int16Array ) denominator = 32767;
	else if ( array instanceof Int32Array ) denominator = 2147483647;
	else console.error( 'THREE.WebGLMorphtargets: Unsupported morph attribute data type: ', array );

	morph.divideScalar( denominator );

}

function WebGLMorphtargets( gl, capabilities, textures ) {

	const influencesList = {};
	const morphInfluences = new Float32Array( 8 );
	const morphTextures = new WeakMap();
	const morph = new Vector4();

	const workInfluences = [];

	for ( let i = 0; i < 8; i ++ ) {

		workInfluences[ i ] = [ i, 0 ];

	}

	function update( object, geometry, material, program ) {

		const objectInfluences = object.morphTargetInfluences;

		if ( capabilities.isWebGL2 === true ) {

			// instead of using attributes, the WebGL 2 code path encodes morph targets
			// into an array of data textures. Each layer represents a single morph target.

			const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
			const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;

			let entry = morphTextures.get( geometry );

			if ( entry === undefined || entry.count !== morphTargetsCount ) {

				if ( entry !== undefined ) entry.texture.dispose();

				const hasMorphPosition = geometry.morphAttributes.position !== undefined;
				const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
				const hasMorphColors = geometry.morphAttributes.color !== undefined;

				const morphTargets = geometry.morphAttributes.position || [];
				const morphNormals = geometry.morphAttributes.normal || [];
				const morphColors = geometry.morphAttributes.color || [];

				let vertexDataCount = 0;

				if ( hasMorphPosition === true ) vertexDataCount = 1;
				if ( hasMorphNormals === true ) vertexDataCount = 2;
				if ( hasMorphColors === true ) vertexDataCount = 3;

				let width = geometry.attributes.position.count * vertexDataCount;
				let height = 1;

				if ( width > capabilities.maxTextureSize ) {

					height = Math.ceil( width / capabilities.maxTextureSize );
					width = capabilities.maxTextureSize;

				}

				const buffer = new Float32Array( width * height * 4 * morphTargetsCount );

				const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount );
				texture.format = RGBAFormat; // using RGBA since RGB might be emulated (and is thus slower)
				texture.type = FloatType;
				texture.needsUpdate = true;

				// fill buffer

				const vertexDataStride = vertexDataCount * 4;

				for ( let i = 0; i < morphTargetsCount; i ++ ) {

					const morphTarget = morphTargets[ i ];
					const morphNormal = morphNormals[ i ];
					const morphColor = morphColors[ i ];

					const offset = width * height * 4 * i;

					for ( let j = 0; j < morphTarget.count; j ++ ) {

						const stride = j * vertexDataStride;

						if ( hasMorphPosition === true ) {

							morph.fromBufferAttribute( morphTarget, j );

							if ( morphTarget.normalized === true ) denormalize( morph, morphTarget );

							buffer[ offset + stride + 0 ] = morph.x;
							buffer[ offset + stride + 1 ] = morph.y;
							buffer[ offset + stride + 2 ] = morph.z;
							buffer[ offset + stride + 3 ] = 0;

						}

						if ( hasMorphNormals === true ) {

							morph.fromBufferAttribute( morphNormal, j );

							if ( morphNormal.normalized === true ) denormalize( morph, morphNormal );

							buffer[ offset + stride + 4 ] = morph.x;
							buffer[ offset + stride + 5 ] = morph.y;
							buffer[ offset + stride + 6 ] = morph.z;
							buffer[ offset + stride + 7 ] = 0;

						}

						if ( hasMorphColors === true ) {

							morph.fromBufferAttribute( morphColor, j );

							if ( morphColor.normalized === true ) denormalize( morph, morphColor );

							buffer[ offset + stride + 8 ] = morph.x;
							buffer[ offset + stride + 9 ] = morph.y;
							buffer[ offset + stride + 10 ] = morph.z;
							buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1;

						}

					}

				}

				entry = {
					count: morphTargetsCount,
					texture: texture,
					size: new Vector2( width, height )
				};

				morphTextures.set( geometry, entry );

				function disposeTexture() {

					texture.dispose();

					morphTextures.delete( geometry );

					geometry.removeEventListener( 'dispose', disposeTexture );

				}

				geometry.addEventListener( 'dispose', disposeTexture );

			}

			//

			let morphInfluencesSum = 0;

			for ( let i = 0; i < objectInfluences.length; i ++ ) {

				morphInfluencesSum += objectInfluences[ i ];

			}

			const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;

			program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
			program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );

			program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures );
			program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size );

		} else {

			// When object doesn't have morph target influences defined, we treat it as a 0-length array
			// This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences

			const length = objectInfluences === undefined ? 0 : objectInfluences.length;

			let influences = influencesList[ geometry.id ];

			if ( influences === undefined || influences.length !== length ) {

				// initialise list

				influences = [];

				for ( let i = 0; i < length; i ++ ) {

					influences[ i ] = [ i, 0 ];

				}

				influencesList[ geometry.id ] = influences;

			}

			// Collect influences

			for ( let i = 0; i < length; i ++ ) {

				const influence = influences[ i ];

				influence[ 0 ] = i;
				influence[ 1 ] = objectInfluences[ i ];

			}

			influences.sort( absNumericalSort );

			for ( let i = 0; i < 8; i ++ ) {

				if ( i < length && influences[ i ][ 1 ] ) {

					workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
					workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];

				} else {

					workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
					workInfluences[ i ][ 1 ] = 0;

				}

			}

			workInfluences.sort( numericalSort );

			const morphTargets = geometry.morphAttributes.position;
			const morphNormals = geometry.morphAttributes.normal;

			let morphInfluencesSum = 0;

			for ( let i = 0; i < 8; i ++ ) {

				const influence = workInfluences[ i ];
				const index = influence[ 0 ];
				const value = influence[ 1 ];

				if ( index !== Number.MAX_SAFE_INTEGER && value ) {

					if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {

						geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );

					}

					if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {

						geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );

					}

					morphInfluences[ i ] = value;
					morphInfluencesSum += value;

				} else {

					if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {

						geometry.deleteAttribute( 'morphTarget' + i );

					}

					if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {

						geometry.deleteAttribute( 'morphNormal' + i );

					}

					morphInfluences[ i ] = 0;

				}

			}

			// GLSL shader uses formula baseinfluence * base + sum(target * influence)
			// This allows us to switch between absolute morphs and relative morphs without changing shader code
			// When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence)
			const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;

			program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
			program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );

		}

	}

	return {

		update: update

	};

}

export { WebGLMorphtargets };
<|endoftext|>


<commit_before>const demand = require('must');
const expect = require('must');

describe('Main Entry point', function() {
    describe('with no auth', function() {
        const api = require('./../lib/index.js');
        const testApi = api('www.google.com');

        it('should be a ApiOptions function', function() {
            demand(api).must.be.a.function();
        });

        it('should return an ApiObject', function() {
            demand(testApi).must.be.a.object();
        });

        describe(': apiObject', function() {
            it('should have a fetch method', function() {
                demand(testApi.fetch).must.be.function();
            });

            it('should return result when fetch method is called correctly simple', function() {
                testApi.fetch('search?q=javascript', function(err, result) {
                    demand(result).to.be.object();
                });
            });

            it('should return result when fetch method is called multiple correctly ', function(done) {
                testApi.fetch('search?q=javascript', function(err, result) {
                    demand(result).to.be.object();

                    testApi.fetch('search?q=csharp', function(err, result) {
                        demand(result).to.be.object();

                        testApi.fetch('search?q=javascript', function(err, result) {
                            demand(result).to.be.object();
                            expect(result.fromCache).to.equal(true);
                            done();
                        });
                    });
                });
            });

            it('should callback with error when fetch method is called incorrectly', function() {
                testApi.fetch('test', function(err, result) {
                    demand(err).to.be.error();
                    demand(result).to.be.undefined();
                });
            });
        });
    });
});
<commit_msg>delete second must<commit_after>const demand = require('must');

describe('Main Entry point', function() {
    describe('with no auth', function() {
        const api = require('./../lib/index.js');
        const testApi = api('www.google.com');

        it('should be a ApiOptions function', function() {
            demand(api).must.be.a.function();
        });

        it('should return an ApiObject', function() {
            demand(testApi).must.be.a.object();
        });

        describe(': apiObject', function() {
            it('should have a fetch method', function() {
                demand(testApi.fetch).must.be.function();
            });

            it('should return result when fetch method is called correctly simple', function() {
                testApi.fetch('search?q=javascript', function(err, result) {
                    demand(result).to.be.object();
                });
            });

            it('should return result when fetch method is called multiple times', function(done) {
                testApi.fetch('search?q=javascript', function(err, result) {
                    demand(result).to.be.object();

                    testApi.fetch('search?q=csharp', function(err, result) {
                        demand(result).to.be.object();

                        testApi.fetch('search?q=javascript', function(err, result) {
                            demand(result).to.be.object();
                            demand(result.fromCache).to.equal(true);
                            done();
                        });
                    });
                });
            });

            it('should callback with error when fetch method is called incorrectly', function() {
                testApi.fetch('test', function(err, result) {
                    demand(err).to.be.error();
                    demand(result).to.be.undefined();
                });
            });
        });
    });
});
<|endoftext|>