<commit_before>const should = require('should');
const path = require('path');
const assert = require('assert');
const {parse} = require('../index');
const csvPath = path.join(__dirname, '/fixtures/');

describe('parse', function () {
    const DEFAULT_HEADER_MAPPING = {
        email: 'email',
        name: 'name'
    };

    it('empty file', async function () {
        const filePath = csvPath + 'empty.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        should.exist(result);
        result.length.should.eql(0);
    });

    it('one column', async function () {
        const filePath = csvPath + 'single-column-with-header.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        should.exist(result);
        result.length.should.eql(2);
        result[0].email.should.eql('jbloggs@example.com');
        result[1].email.should.eql('test@example.com');
    });

    it('one column without header mapping returns empty result', async function () {
        const filePath = csvPath + 'single-column-with-header.csv';
        const result = await parse(filePath);

        should.exist(result);
        result.length.should.eql(0);
    });

    it('two columns, 1 filter', async function () {
        const filePath = csvPath + 'two-columns-with-header.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        should.exist(result);
        result.length.should.eql(2);
        result[0].email.should.eql('jbloggs@example.com');
        result[1].email.should.eql('test@example.com');
    });

    it('two columns, 2 filters', async function () {
        const filePath = csvPath + 'two-columns-obscure-header.csv';
        const mapping = {
            id: 'id',
            'Email Address': 'email'
        };
        const result = await parse(filePath, mapping);

        should.exist(result);
        result.length.should.eql(2);
        result[0].email.should.eql('jbloggs@example.com');
        result[0].id.should.eql('1');
        result[1].email.should.eql('test@example.com');
        result[1].id.should.eql('2');
    });

    it('two columns with mapping', async function () {
        const filePath = csvPath + 'two-columns-mapping-header.csv';
        const mapping = {
            id: 'id',
            correo_electronico: 'email',
            nombre: 'name'
        };
        const result = await parse(filePath, mapping);

        should.exist(result);
        result.length.should.eql(2);
        result[0].email.should.eql('jbloggs@example.com');
        result[0].name.should.eql('joe');
        result[0].id.should.eql('1');

        result[1].email.should.eql('test@example.com');
        result[1].name.should.eql('test');
        result[1].id.should.eql('2');
    });

    it('two columns with partial mapping', async function () {
        const filePath = csvPath + 'two-columns-mapping-header.csv';
        const mapping = {
            id: 'id',
            correo_electronico: 'email'
        };
        const result = await parse(filePath, mapping);

        should.exist(result);
        result.length.should.eql(2);
        result[0].email.should.eql('jbloggs@example.com');
        result[0].id.should.eql('1');

        result[1].email.should.eql('test@example.com');
        result[1].id.should.eql('2');
    });

    it('transforms empty values to nulls', async function () {
        const filePath = csvPath + 'multiple-records-with-empty-values.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        should.exist(result);
        result.length.should.eql(2);
        result[0].email.should.eql('jbloggs@example.com');
        result[0].name.should.eql('Bob');

        result[1].email.should.eql('test@example.com');
        should.equal(result[1].name, null);
    });

    it(' transforms "subscribed_to_emails" column to "subscribed" property when the mapping is passed in', async function () {
        const filePath = csvPath + 'subscribed-to-emails-header.csv';
        const mapping = {
            email: 'email',
            subscribed_to_emails: 'subscribed'
        };
        const result = await parse(filePath, mapping);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.ok(result[0].subscribed);

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].subscribed, false);
    });

    it('DOES NOT transforms "subscribed_to_emails" column to "subscribed" property when the WITHOUT mapping', async function () {
        const filePath = csvPath + 'subscribed-to-emails-header.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[0].subscribed_to_emails, undefined, 'property not present in the mapping should not be defined');

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].subscribed_to_emails, undefined, 'property not present in the mapping should not be defined');
    });
});
<commit_msg>Migrated CSV parser tests to 'assert'<commit_after>const path = require('path');
const assert = require('assert');
const {parse} = require('../index');
const csvPath = path.join(__dirname, '/fixtures/');

describe('parse', function () {
    const DEFAULT_HEADER_MAPPING = {
        email: 'email',
        name: 'name'
    };

    it('empty file', async function () {
        const filePath = csvPath + 'empty.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        assert.ok(result);
        assert.equal(result.length, 0);
    });

    it('one column', async function () {
        const filePath = csvPath + 'single-column-with-header.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[1].email, 'test@example.com');
    });

    it('one column without header mapping returns empty result', async function () {
        const filePath = csvPath + 'single-column-with-header.csv';
        const result = await parse(filePath);

        assert.ok(result);
        assert.equal(result.length, 0);
    });

    it('two columns, 1 filter', async function () {
        const filePath = csvPath + 'two-columns-with-header.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[1].email, 'test@example.com');
    });

    it('two columns, 2 filters', async function () {
        const filePath = csvPath + 'two-columns-obscure-header.csv';
        const mapping = {
            id: 'id',
            'Email Address': 'email'
        };
        const result = await parse(filePath, mapping);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[0].id, '1');
        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].id, '2');
    });

    it('two columns with mapping', async function () {
        const filePath = csvPath + 'two-columns-mapping-header.csv';
        const mapping = {
            id: 'id',
            correo_electronico: 'email',
            nombre: 'name'
        };
        const result = await parse(filePath, mapping);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[0].name, 'joe');
        assert.equal(result[0].id, '1');

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].name, 'test');
        assert.equal(result[1].id, '2');
    });

    it('two columns with partial mapping', async function () {
        const filePath = csvPath + 'two-columns-mapping-header.csv';
        const mapping = {
            id: 'id',
            correo_electronico: 'email'
        };
        const result = await parse(filePath, mapping);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[0].id, '1');

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].id, '2');
    });

    it('transforms empty values to nulls', async function () {
        const filePath = csvPath + 'multiple-records-with-empty-values.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[0].name, 'Bob');

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].name, null);
    });

    it(' transforms "subscribed_to_emails" column to "subscribed" property when the mapping is passed in', async function () {
        const filePath = csvPath + 'subscribed-to-emails-header.csv';
        const mapping = {
            email: 'email',
            subscribed_to_emails: 'subscribed'
        };
        const result = await parse(filePath, mapping);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.ok(result[0].subscribed);

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].subscribed, false);
    });

    it('DOES NOT transforms "subscribed_to_emails" column to "subscribed" property when the WITHOUT mapping', async function () {
        const filePath = csvPath + 'subscribed-to-emails-header.csv';
        const result = await parse(filePath, DEFAULT_HEADER_MAPPING);

        assert.ok(result);
        assert.equal(result.length, 2);
        assert.equal(result[0].email, 'jbloggs@example.com');
        assert.equal(result[0].subscribed_to_emails, undefined, 'property not present in the mapping should not be defined');

        assert.equal(result[1].email, 'test@example.com');
        assert.equal(result[1].subscribed_to_emails, undefined, 'property not present in the mapping should not be defined');
    });
});
<|endoftext|>


<commit_before>import fs from 'fs';
import path from 'path';
import temp from 'temp';
import until from 'test-until';

import {cloneRepository} from './helpers';
import GithubPackage from '../lib/github-package';

describe('GithubPackage', function() {
  let atomEnv, workspace, project, commandRegistry, notificationManager, githubPackage;

  beforeEach(function() {
    atomEnv = global.buildAtomEnvironment();
    workspace = atomEnv.workspace;
    project = atomEnv.project;
    commandRegistry = atomEnv.commands;
    notificationManager = atomEnv.notifications;
    githubPackage = new GithubPackage(workspace, project, commandRegistry, notificationManager);
  });

  afterEach(async function() {
    await githubPackage.deactivate();
    atomEnv.destroy();
  });

  describe('activate()', function() {
    it('updates the active repository', async function() {
      await githubPackage.activate();
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      project.setPaths([workdirPath1, workdirPath2]);
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'change 1', 'utf8');
      fs.writeFileSync(path.join(workdirPath1, 'b.txt'), 'change 2', 'utf8');

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      const repository = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      await until(() => githubPackage.getActiveRepository() === repository);
    });
  });

  describe('changing the project paths', function() {
    it('updates the active repository', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      const nonRepositoryPath = temp.mkdirSync();
      fs.writeFileSync(path.join(nonRepositoryPath, 'c.txt'));
      project.setPaths([workdirPath1, workdirPath2, nonRepositoryPath]);
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'change 1', 'utf8');
      await githubPackage.activate();

      sinon.spy(githubPackage, 'rerender');
      await workspace.open(path.join(workdirPath1, 'a.txt'));
      const repository1 = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      await until(() => githubPackage.getActiveRepository() === repository1);
      assert.equal(githubPackage.rerender.callCount, 1);

      // Remove repository for open file
      project.setPaths([workdirPath2, nonRepositoryPath]);
      await until(() => githubPackage.getActiveRepository() === null);
      assert.equal(githubPackage.rerender.callCount, 2);

      await workspace.open(path.join(workdirPath2, 'b.txt'));
      const repository2 = await githubPackage.getRepositoryForWorkdirPath(workdirPath2);
      await until(() => githubPackage.getActiveRepository() === repository2);
      assert.equal(githubPackage.rerender.callCount, 3);

      await workspace.open(path.join(nonRepositoryPath, 'c.txt'));
      await until(() => githubPackage.getActiveRepository() === null);
      assert.equal(githubPackage.rerender.callCount, 4);
    });

    it('destroys all the repositories associated with the removed project folders', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      const workdirPath3 = await cloneRepository('three-files');
      project.setPaths([workdirPath1, workdirPath2, workdirPath3]);
      await githubPackage.activate();

      const repository1 = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      const repository2 = await githubPackage.getRepositoryForWorkdirPath(workdirPath2);
      const repository3 = await githubPackage.getRepositoryForWorkdirPath(workdirPath3);
      assert.isOk(repository1);
      assert.isOk(repository2);
      assert.isOk(repository3);

      sinon.stub(repository1, 'destroy');
      sinon.stub(repository2, 'destroy');
      sinon.stub(repository3, 'destroy');

      project.removePath(workdirPath1);
      project.removePath(workdirPath3);

      await until(() => repository1.destroy.callCount === 1, 'repository1 is destroyed');
      await until(() => repository3.destroy.callCount === 1, 'repository3 is destroyed');
      assert.equal(repository2.destroy.callCount, 0);
      assert.notEqual(await githubPackage.getRepositoryForWorkdirPath(repository1.getWorkingDirectoryPath()), repository1);
      assert.notEqual(await githubPackage.getRepositoryForWorkdirPath(repository3.getWorkingDirectoryPath()), repository3);
      assert.equal(await githubPackage.getRepositoryForWorkdirPath(repository2.getWorkingDirectoryPath()), repository2);
    });
  });

  describe('didChangeActivePaneItem()', function() {
    it('updates the active repository', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      project.setPaths([workdirPath1, workdirPath2]);
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'change 1', 'utf8');
      fs.writeFileSync(path.join(workdirPath2, 'b.txt'), 'change 2', 'utf8');

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      githubPackage.didChangeActivePaneItem();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath1));

      await workspace.open(path.join(workdirPath2, 'b.txt'));
      githubPackage.didChangeActivePaneItem();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath2));
    });
  });

  describe('updateActiveRepository()', function() {
    it('updates the active repository based on the active item, setting it to null when the active item is not in a project repository', async function() {
      // TODO: FIXME Warning: Possible EventEmitter memory leak detected. 11 ipc-helpers-window-method-response listeners added.
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      const nonRepositoryPath = temp.mkdirSync();
      fs.writeFileSync(path.join(nonRepositoryPath, 'c.txt'));
      project.setPaths([workdirPath1, workdirPath2, nonRepositoryPath]);
      await githubPackage.activate();

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      await workspace.open(path.join(workdirPath2, 'b.txt'));

      await githubPackage.updateActiveRepository();
      assert.isNotNull(githubPackage.getActiveRepository());
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath2));

      await workspace.open(path.join(nonRepositoryPath, 'c.txt'));
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      await githubPackage.updateActiveRepository();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath1));

      workspace.getActivePane().activateItem({}); // such as when find & replace results pane is focused
      await githubPackage.updateActiveRepository();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath1));

      await workspace.open(path.join(workdirPath2, 'b.txt'));
      await githubPackage.updateActiveRepository();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath2));

      project.removePath(workdirPath2);
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());

      project.removePath(workdirPath1);
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());
    });
  });

  describe('when there is a change in the repository', function() {
    let workdirPath1, atomGitRepository1, repository1;
    let workdirPath2, atomGitRepository2, repository2;

    beforeEach(async function() {
      workdirPath1 = await cloneRepository('three-files');
      workdirPath2 = await cloneRepository('three-files');

      fs.writeFileSync(path.join(workdirPath1, 'c.txt'), 'ch-ch-ch-changes', 'utf8');
      fs.writeFileSync(path.join(workdirPath2, 'c.txt'), 'ch-ch-ch-changes', 'utf8');

      project.setPaths([workdirPath1, workdirPath2]);
      await githubPackage.activate();
      await githubPackage.getInitialWatchersStartedPromise();
      [atomGitRepository1, atomGitRepository2] = githubPackage.project.getRepositories();
      sinon.stub(atomGitRepository1, 'refreshStatus');
      sinon.stub(atomGitRepository2, 'refreshStatus');

      repository1 = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      repository2 = await githubPackage.getRepositoryForWorkdirPath(workdirPath2);
      sinon.stub(repository1, 'refresh');
      sinon.stub(repository2, 'refresh');
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a file is changed in workspace 1', async function() {
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'some changes', 'utf8');

      await assert.async.isTrue(repository1.refresh.called);
      await assert.async.isTrue(atomGitRepository1.refreshStatus.called);
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a file is changed in workspace 2', async function() {
      fs.writeFileSync(path.join(workdirPath2, 'b.txt'), 'other changes', 'utf8');

      await assert.async.isTrue(repository2.refresh.called);
      await assert.async.isTrue(atomGitRepository2.refreshStatus.called);
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a commit is made in workspace 1', async function() {
      await repository1.git.exec(['commit', '-am', 'commit in repository1']);

      await assert.async.isTrue(repository1.refresh.called);
      await assert.async.isTrue(atomGitRepository1.refreshStatus.called);
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a commit is made in workspace 2', async function() {
      await repository2.git.exec(['commit', '-am', 'commit in repository2']);

      await assert.async.isTrue(repository2.refresh.called);
      await assert.async.isTrue(atomGitRepository2.refreshStatus.called);
    });
  });

  describe('#projectPathForItemPath', function() {
    it('does not error when the path is falsy (e.g. an empty text editor)', function() {
      sinon.stub(project, 'getPaths').returns(['path']);
      assert.doesNotThrow(() => {
        githubPackage.projectPathForItemPath(null);
      });
    });

    it('returns the correct path when the item path starts with the project path but the item path is not in the project', function() {
      sinon.stub(project, 'getPaths').returns([path.join('path', 'to', 'my'), path.join('path', 'to', 'my-project')]);
      assert.equal(githubPackage.projectPathForItemPath(path.join('path', 'to', 'my-project', 'file.txt')), path.join('path', 'to', 'my-project'));
    });
  });
});
<commit_msg>Remove TODO for warning message that is fixed in atom/atom<commit_after>import fs from 'fs';
import path from 'path';
import temp from 'temp';
import until from 'test-until';

import {cloneRepository} from './helpers';
import GithubPackage from '../lib/github-package';

describe('GithubPackage', function() {
  let atomEnv, workspace, project, commandRegistry, notificationManager, githubPackage;

  beforeEach(function() {
    atomEnv = global.buildAtomEnvironment();
    workspace = atomEnv.workspace;
    project = atomEnv.project;
    commandRegistry = atomEnv.commands;
    notificationManager = atomEnv.notifications;
    githubPackage = new GithubPackage(workspace, project, commandRegistry, notificationManager);
  });

  afterEach(async function() {
    await githubPackage.deactivate();
    atomEnv.destroy();
  });

  describe('activate()', function() {
    it('updates the active repository', async function() {
      await githubPackage.activate();
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      project.setPaths([workdirPath1, workdirPath2]);
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'change 1', 'utf8');
      fs.writeFileSync(path.join(workdirPath1, 'b.txt'), 'change 2', 'utf8');

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      const repository = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      await until(() => githubPackage.getActiveRepository() === repository);
    });
  });

  describe('changing the project paths', function() {
    it('updates the active repository', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      const nonRepositoryPath = temp.mkdirSync();
      fs.writeFileSync(path.join(nonRepositoryPath, 'c.txt'));
      project.setPaths([workdirPath1, workdirPath2, nonRepositoryPath]);
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'change 1', 'utf8');
      await githubPackage.activate();

      sinon.spy(githubPackage, 'rerender');
      await workspace.open(path.join(workdirPath1, 'a.txt'));
      const repository1 = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      await until(() => githubPackage.getActiveRepository() === repository1);
      assert.equal(githubPackage.rerender.callCount, 1);

      // Remove repository for open file
      project.setPaths([workdirPath2, nonRepositoryPath]);
      await until(() => githubPackage.getActiveRepository() === null);
      assert.equal(githubPackage.rerender.callCount, 2);

      await workspace.open(path.join(workdirPath2, 'b.txt'));
      const repository2 = await githubPackage.getRepositoryForWorkdirPath(workdirPath2);
      await until(() => githubPackage.getActiveRepository() === repository2);
      assert.equal(githubPackage.rerender.callCount, 3);

      await workspace.open(path.join(nonRepositoryPath, 'c.txt'));
      await until(() => githubPackage.getActiveRepository() === null);
      assert.equal(githubPackage.rerender.callCount, 4);
    });

    it('destroys all the repositories associated with the removed project folders', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      const workdirPath3 = await cloneRepository('three-files');
      project.setPaths([workdirPath1, workdirPath2, workdirPath3]);
      await githubPackage.activate();

      const repository1 = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      const repository2 = await githubPackage.getRepositoryForWorkdirPath(workdirPath2);
      const repository3 = await githubPackage.getRepositoryForWorkdirPath(workdirPath3);
      assert.isOk(repository1);
      assert.isOk(repository2);
      assert.isOk(repository3);

      sinon.stub(repository1, 'destroy');
      sinon.stub(repository2, 'destroy');
      sinon.stub(repository3, 'destroy');

      project.removePath(workdirPath1);
      project.removePath(workdirPath3);

      await until(() => repository1.destroy.callCount === 1, 'repository1 is destroyed');
      await until(() => repository3.destroy.callCount === 1, 'repository3 is destroyed');
      assert.equal(repository2.destroy.callCount, 0);
      assert.notEqual(await githubPackage.getRepositoryForWorkdirPath(repository1.getWorkingDirectoryPath()), repository1);
      assert.notEqual(await githubPackage.getRepositoryForWorkdirPath(repository3.getWorkingDirectoryPath()), repository3);
      assert.equal(await githubPackage.getRepositoryForWorkdirPath(repository2.getWorkingDirectoryPath()), repository2);
    });
  });

  describe('didChangeActivePaneItem()', function() {
    it('updates the active repository', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      project.setPaths([workdirPath1, workdirPath2]);
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'change 1', 'utf8');
      fs.writeFileSync(path.join(workdirPath2, 'b.txt'), 'change 2', 'utf8');

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      githubPackage.didChangeActivePaneItem();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath1));

      await workspace.open(path.join(workdirPath2, 'b.txt'));
      githubPackage.didChangeActivePaneItem();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath2));
    });
  });

  describe('updateActiveRepository()', function() {
    it('updates the active repository based on the active item, setting it to null when the active item is not in a project repository', async function() {
      const workdirPath1 = await cloneRepository('three-files');
      const workdirPath2 = await cloneRepository('three-files');
      const nonRepositoryPath = temp.mkdirSync();
      fs.writeFileSync(path.join(nonRepositoryPath, 'c.txt'));
      project.setPaths([workdirPath1, workdirPath2, nonRepositoryPath]);
      await githubPackage.activate();

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      await workspace.open(path.join(workdirPath2, 'b.txt'));

      await githubPackage.updateActiveRepository();
      assert.isNotNull(githubPackage.getActiveRepository());
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath2));

      await workspace.open(path.join(nonRepositoryPath, 'c.txt'));
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      await githubPackage.updateActiveRepository();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath1));

      workspace.getActivePane().activateItem({}); // such as when find & replace results pane is focused
      await githubPackage.updateActiveRepository();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath1));

      await workspace.open(path.join(workdirPath2, 'b.txt'));
      await githubPackage.updateActiveRepository();
      assert.equal(githubPackage.getActiveRepository(), await githubPackage.getRepositoryForWorkdirPath(workdirPath2));

      project.removePath(workdirPath2);
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());

      project.removePath(workdirPath1);
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());

      await workspace.open(path.join(workdirPath1, 'a.txt'));
      await githubPackage.updateActiveRepository();
      assert.isNull(githubPackage.getActiveRepository());
    });
  });

  describe('when there is a change in the repository', function() {
    let workdirPath1, atomGitRepository1, repository1;
    let workdirPath2, atomGitRepository2, repository2;

    beforeEach(async function() {
      workdirPath1 = await cloneRepository('three-files');
      workdirPath2 = await cloneRepository('three-files');

      fs.writeFileSync(path.join(workdirPath1, 'c.txt'), 'ch-ch-ch-changes', 'utf8');
      fs.writeFileSync(path.join(workdirPath2, 'c.txt'), 'ch-ch-ch-changes', 'utf8');

      project.setPaths([workdirPath1, workdirPath2]);
      await githubPackage.activate();
      await githubPackage.getInitialWatchersStartedPromise();
      [atomGitRepository1, atomGitRepository2] = githubPackage.project.getRepositories();
      sinon.stub(atomGitRepository1, 'refreshStatus');
      sinon.stub(atomGitRepository2, 'refreshStatus');

      repository1 = await githubPackage.getRepositoryForWorkdirPath(workdirPath1);
      repository2 = await githubPackage.getRepositoryForWorkdirPath(workdirPath2);
      sinon.stub(repository1, 'refresh');
      sinon.stub(repository2, 'refresh');
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a file is changed in workspace 1', async function() {
      fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'some changes', 'utf8');

      await assert.async.isTrue(repository1.refresh.called);
      await assert.async.isTrue(atomGitRepository1.refreshStatus.called);
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a file is changed in workspace 2', async function() {
      fs.writeFileSync(path.join(workdirPath2, 'b.txt'), 'other changes', 'utf8');

      await assert.async.isTrue(repository2.refresh.called);
      await assert.async.isTrue(atomGitRepository2.refreshStatus.called);
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a commit is made in workspace 1', async function() {
      await repository1.git.exec(['commit', '-am', 'commit in repository1']);

      await assert.async.isTrue(repository1.refresh.called);
      await assert.async.isTrue(atomGitRepository1.refreshStatus.called);
    });

    it('refreshes the appropriate Repository and Atom GitRepository when a commit is made in workspace 2', async function() {
      await repository2.git.exec(['commit', '-am', 'commit in repository2']);

      await assert.async.isTrue(repository2.refresh.called);
      await assert.async.isTrue(atomGitRepository2.refreshStatus.called);
    });
  });

  describe('#projectPathForItemPath', function() {
    it('does not error when the path is falsy (e.g. an empty text editor)', function() {
      sinon.stub(project, 'getPaths').returns(['path']);
      assert.doesNotThrow(() => {
        githubPackage.projectPathForItemPath(null);
      });
    });

    it('returns the correct path when the item path starts with the project path but the item path is not in the project', function() {
      sinon.stub(project, 'getPaths').returns([path.join('path', 'to', 'my'), path.join('path', 'to', 'my-project')]);
      assert.equal(githubPackage.projectPathForItemPath(path.join('path', 'to', 'my-project', 'file.txt')), path.join('path', 'to', 'my-project'));
    });
  });
});
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var errors = require('./errors.js');

var TOMBSTONE_TTL_OFFSET = 500;

module.exports = Operations;

function Operations(opts) {
    var self = this;

    self.timers = opts.timers;
    self.logger = opts.logger;
    self.random = opts.random;
    self.connectionStalePeriod = opts.connectionStalePeriod;

    self.connection = opts.connection;
    self.destroyed = false; // TODO need this?

    self.requests = {
        in: Object.create(null),
        out: Object.create(null)
    };
    self.pending = {
        in: 0,
        out: 0
    };
    self.lastTimeoutTime = 0;
}

function OperationTombstone(operations, id, time, req) {
    var self = this;

    self.type = 'tchannel.operation.tombstone';
    self.isTombstone = true;
    self.logger = operations.logger;
    self.operations = operations;
    self.id = id;
    self.time = time;
    self.timeout = TOMBSTONE_TTL_OFFSET + req.timeout;
    self.timeHeapHandle = null;
    self.destroyed = false;
    self.serviceName = req.serviceName;
    self.callerName = req.headers.cn;
    self.endpoint = req.endpoint;
}

OperationTombstone.prototype.extendLogInfo = function extendLogInfo(info) {
    var self = this;

    var conn = self.operations && self.operations.connection;

    info.id = self.id;
    info.serviceName = self.serviceName;
    info.callerName = self.callerName;
    info.endpoint = self.endpoint;
    info.tombstoneTime = self.time;
    info.tombstoneTTL = self.timeout;
    info.heapCanceled = self.timeHeapHandle && !self.timeHeapHandle.item;
    info.heapExpireTime = self.timeHeapHandle && self.timeHeapHandle.expireTime;
    info.heapAmItem = self.timeHeapHandle && self.timeHeapHandle.item === self;
    info.hostPort = conn && conn.channel.hostPort;
    info.socketRemoteAddr = conn && conn.socketRemoteAddr;
    info.remoteName = conn && conn.remoteName;
    info.connClosing = conn && conn.closing;

    var other = self.operations && self.operations.requests.out[self.id];
    if (self !== other) {
        info.otherType = typeof other;
        info.otherConstructorName = other && other.constructor && other.constructor.name;
    }

    return info;
};

OperationTombstone.prototype.destroy = function destroy(now) {
    var self = this;

    self.destroyed = true;

    self.onTimeout(now);
};

OperationTombstone.prototype.onTimeout = function onTimeout(now) {
    var self = this;

    if (!self.destroyed && now < self.timeout + self.time) {
        self.logger.error('tombstone timed out too early', self.extendLogInfo({
            now: now,
            expireTime: self.timeout + self.time,
            delta: (self.timeout + self.time) - now
        }));
    }

    if (self.operations &&
        self.operations.requests.out[self.id] === self) {
        delete self.operations.requests.out[self.id];
        self.operations = null;
    } else {
        self.logger.warn('mismatched expired operation tombstone', self.extendLogInfo({}));
        self.operations = null;
    }

    self.timeHeapHandle = null;
};

Operations.prototype.checkLastTimeoutTime = function checkLastTimeoutTime(now) {
    var self = this;

    if (self.lastTimeoutTime &&
        now > self.lastTimeoutTime + self.connectionStalePeriod
    ) {
        self._deferResetDueToTimeouts(now);
    } else if (!self.lastTimeoutTime) {
        self.lastTimeoutTime = now;
    }
};

Operations.prototype._deferResetDueToTimeouts = function _deferResetDueToTimeouts(now) {
    var self = this;

    var elapsed = now - self.lastTimeoutTime;
    var err = errors.ConnectionStaleTimeoutError({
        period: self.connectionStalePeriod,
        elapsed: elapsed,
        lastTimeoutTime: self.lastTimeoutTime
    });
    process.nextTick(opCheckLastTimedout);

    function opCheckLastTimedout() {
        self.logger.warn('destroying socket from timeouts', self.connection.extendLogInfo({
            error: err
        }));
        self.connection.resetAll(err);
    }
};

Operations.prototype.getRequests = function getRequests() {
    var self = this;

    return self.requests;
};

Operations.prototype.getPending = function getPending() {
    var self = this;

    return self.pending;
};

Operations.prototype.getOutReq = function getOutReq(id) {
    var self = this;

    var req = self.requests.out[id] || null;
    if (req && req.isTombstone) {
        return null;
    } else {
        return req;
    }
};

Operations.prototype.getInReq = function getInReq(id) {
    var self = this;

    return self.requests.in[id];
};

Operations.prototype.addOutReq = function addOutReq(req) {
    var self = this;

    req.operations = self;
    self.requests.out[req.id] = req;
    self.pending.out++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.addInReq = function addInReq(req) {
    var self = this;

    req.operations = self;
    self.requests.in[req.id] = req;
    self.pending.in++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.popOutReq = function popOutReq(id, context) {
    var self = this;

    var req = self.requests.out[id];
    if (!req) {
        self.logMissingOutRequest(id, context);
        return null;
    } else if (req.isTombstone) {
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    } else {
        self.logger.warn('Found OutRequest without timeHeapHandle', {
            serviceName: req.serviceName,
            endpoint: req.endpoint,
            socketRemoteAddr: req.remoteAddr,
            callerName: req.headers.cn
        });
    }

    var tombstone = new OperationTombstone(self, id, self.timers.now(), req);
    self.requests.out[id] = tombstone;
    tombstone.timeHeapHandle = self.connection.channel.timeHeap.update(tombstone, tombstone.time);

    req.operations = null;
    self.pending.out--;

    return req;
};

Operations.prototype.logMissingOutRequest =
function logMissingOutRequest(id, context) {
    var self = this;

    // context is err or res
    if (context && context.originalId) {
        context = {
            error: context,
            id: context.originalId,
            info: 'got error frame for unknown id'
        };
    } else if (context && context.id) {
        context = {
            responseId: context.id,
            code: context.code,
            arg1: Buffer.isBuffer(context.arg1) ?
                String(context.arg1) : 'streamed-arg1',
            info: 'got call response for unknown id'
        };
    }

    // This could be because of a confused / corrupted server.
    self.logger.info('popOutReq received for unknown or lost id', {
        context: context,
        socketRemoteAddr: self.connection.socketRemoteAddr,
        direction: self.connection.direction
    });
};

Operations.prototype.popInReq = function popInReq(id) {
    var self = this;

    var req = self.requests.in[id];
    if (!req) {
        // TODO warn ?
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    }

    delete self.requests.in[id];
    self.pending.in--;

    return req;
};

Operations.prototype.clear = function clear() {
    var self = this;

    var now = self.timers.now();
    var inReqKeys = Object.keys(self.requests.in);
    var outReqKeys = Object.keys(self.requests.out);

    for (var i = 0; i < inReqKeys.length; i++) {
        self.popInReq(inReqKeys[i]);
    }
    for (var j = 0; j < outReqKeys.length; j++) {
        self.popOutReq(outReqKeys[j]);

        var tombstone = self.requests.out[outReqKeys[j]];
        if (tombstone.timeHeapHandle) {
            tombstone.timeHeapHandle.cancel();
        }
        tombstone.destroy(now);
    }
};

Operations.prototype.destroy = function destroy() {
    var self = this;

    self.destroyed = true;
};

Operations.prototype.sanitySweep = function sanitySweep() {
    var self = this;

    self._sweepOps(self.requests.in, 'in');
    self._sweepOps(self.requests.out, 'out');
};

Operations.prototype._sweepOps = function _sweepOps(ops, direction) {
    var self = this;

    var now = self.timers.now();
    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var op = ops[id];
        if (op === undefined) {
            self.logger.warn('unexpected undefined operation', {
                direction: direction,
                id: id
            });
        } else if (op.timedOut) {
            self.logger.warn('lingering timed-out operation', {
                direction: direction,
                id: id
            });

            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        } else if (op.isTombstone) {
            var heap = self.connection.channel.timeHeap;
            var expireTime = op.time + op.timeout;

            if (!op.operations) {
                self.logger.warn('zombie tombstone', op.extendLogInfo({
                    direction: direction,
                    opKey: id
                }));
                delete ops[id];
                op.operations = null;
                op.timeHeapHandle.cancel();
                op.timeHeapHandle = null;
            } else if (expireTime < now && heap.lastRun > expireTime) {
                self.logger.warn('stale tombstone', op.extendLogInfo({
                    direction: direction,
                    opKey: id,
                    now: now,
                    staleDelta: op.time + op.timeout - now,
                    expireTime: expireTime,
                    heapLastRun: heap.lastRun
                }));
                delete ops[id];
                op.operations = null;
                op.timeHeapHandle.cancel();
                op.timeHeapHandle = null;
            }
        }
    }
};
<commit_msg>OperationTombstone#extendLogInfo: refactor branching<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var errors = require('./errors.js');

var TOMBSTONE_TTL_OFFSET = 500;

module.exports = Operations;

function Operations(opts) {
    var self = this;

    self.timers = opts.timers;
    self.logger = opts.logger;
    self.random = opts.random;
    self.connectionStalePeriod = opts.connectionStalePeriod;

    self.connection = opts.connection;
    self.destroyed = false; // TODO need this?

    self.requests = {
        in: Object.create(null),
        out: Object.create(null)
    };
    self.pending = {
        in: 0,
        out: 0
    };
    self.lastTimeoutTime = 0;
}

function OperationTombstone(operations, id, time, req) {
    var self = this;

    self.type = 'tchannel.operation.tombstone';
    self.isTombstone = true;
    self.logger = operations.logger;
    self.operations = operations;
    self.id = id;
    self.time = time;
    self.timeout = TOMBSTONE_TTL_OFFSET + req.timeout;
    self.timeHeapHandle = null;
    self.destroyed = false;
    self.serviceName = req.serviceName;
    self.callerName = req.headers.cn;
    self.endpoint = req.endpoint;
}

OperationTombstone.prototype.extendLogInfo = function extendLogInfo(info) {
    var self = this;

    info.id = self.id;
    info.serviceName = self.serviceName;
    info.callerName = self.callerName;
    info.endpoint = self.endpoint;
    info.tombstoneTime = self.time;
    info.tombstoneTTL = self.timeout;
    info.heapCanceled = self.timeHeapHandle && !self.timeHeapHandle.item;
    info.heapExpireTime = self.timeHeapHandle && self.timeHeapHandle.expireTime;
    info.heapAmItem = self.timeHeapHandle && self.timeHeapHandle.item === self;

    if (self.operations) {
        var conn = self.operations.connection;
        info.hostPort = conn && conn.channel.hostPort;
        info.socketRemoteAddr = conn && conn.socketRemoteAddr;
        info.remoteName = conn && conn.remoteName;
        info.connClosing = conn && conn.closing;

        var other = self.operations.requests.out[self.id];
        if (self !== other) {
            info.otherType = typeof other;
            info.otherConstructorName = other && other.constructor && other.constructor.name;
        }
    }

    return info;
};

OperationTombstone.prototype.destroy = function destroy(now) {
    var self = this;

    self.destroyed = true;

    self.onTimeout(now);
};

OperationTombstone.prototype.onTimeout = function onTimeout(now) {
    var self = this;

    if (!self.destroyed && now < self.timeout + self.time) {
        self.logger.error('tombstone timed out too early', self.extendLogInfo({
            now: now,
            expireTime: self.timeout + self.time,
            delta: (self.timeout + self.time) - now
        }));
    }

    if (self.operations &&
        self.operations.requests.out[self.id] === self) {
        delete self.operations.requests.out[self.id];
        self.operations = null;
    } else {
        self.logger.warn('mismatched expired operation tombstone', self.extendLogInfo({}));
        self.operations = null;
    }

    self.timeHeapHandle = null;
};

Operations.prototype.checkLastTimeoutTime = function checkLastTimeoutTime(now) {
    var self = this;

    if (self.lastTimeoutTime &&
        now > self.lastTimeoutTime + self.connectionStalePeriod
    ) {
        self._deferResetDueToTimeouts(now);
    } else if (!self.lastTimeoutTime) {
        self.lastTimeoutTime = now;
    }
};

Operations.prototype._deferResetDueToTimeouts = function _deferResetDueToTimeouts(now) {
    var self = this;

    var elapsed = now - self.lastTimeoutTime;
    var err = errors.ConnectionStaleTimeoutError({
        period: self.connectionStalePeriod,
        elapsed: elapsed,
        lastTimeoutTime: self.lastTimeoutTime
    });
    process.nextTick(opCheckLastTimedout);

    function opCheckLastTimedout() {
        self.logger.warn('destroying socket from timeouts', self.connection.extendLogInfo({
            error: err
        }));
        self.connection.resetAll(err);
    }
};

Operations.prototype.getRequests = function getRequests() {
    var self = this;

    return self.requests;
};

Operations.prototype.getPending = function getPending() {
    var self = this;

    return self.pending;
};

Operations.prototype.getOutReq = function getOutReq(id) {
    var self = this;

    var req = self.requests.out[id] || null;
    if (req && req.isTombstone) {
        return null;
    } else {
        return req;
    }
};

Operations.prototype.getInReq = function getInReq(id) {
    var self = this;

    return self.requests.in[id];
};

Operations.prototype.addOutReq = function addOutReq(req) {
    var self = this;

    req.operations = self;
    self.requests.out[req.id] = req;
    self.pending.out++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.addInReq = function addInReq(req) {
    var self = this;

    req.operations = self;
    self.requests.in[req.id] = req;
    self.pending.in++;

    req.timeHeapHandle = self.connection.channel.timeHeap.update(req);

    return req;
};

Operations.prototype.popOutReq = function popOutReq(id, context) {
    var self = this;

    var req = self.requests.out[id];
    if (!req) {
        self.logMissingOutRequest(id, context);
        return null;
    } else if (req.isTombstone) {
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    } else {
        self.logger.warn('Found OutRequest without timeHeapHandle', {
            serviceName: req.serviceName,
            endpoint: req.endpoint,
            socketRemoteAddr: req.remoteAddr,
            callerName: req.headers.cn
        });
    }

    var tombstone = new OperationTombstone(self, id, self.timers.now(), req);
    self.requests.out[id] = tombstone;
    tombstone.timeHeapHandle = self.connection.channel.timeHeap.update(tombstone, tombstone.time);

    req.operations = null;
    self.pending.out--;

    return req;
};

Operations.prototype.logMissingOutRequest =
function logMissingOutRequest(id, context) {
    var self = this;

    // context is err or res
    if (context && context.originalId) {
        context = {
            error: context,
            id: context.originalId,
            info: 'got error frame for unknown id'
        };
    } else if (context && context.id) {
        context = {
            responseId: context.id,
            code: context.code,
            arg1: Buffer.isBuffer(context.arg1) ?
                String(context.arg1) : 'streamed-arg1',
            info: 'got call response for unknown id'
        };
    }

    // This could be because of a confused / corrupted server.
    self.logger.info('popOutReq received for unknown or lost id', {
        context: context,
        socketRemoteAddr: self.connection.socketRemoteAddr,
        direction: self.connection.direction
    });
};

Operations.prototype.popInReq = function popInReq(id) {
    var self = this;

    var req = self.requests.in[id];
    if (!req) {
        // TODO warn ?
        return null;
    }

    if (req.timeHeapHandle) {
        req.timeHeapHandle.cancel();
        req.timeHeapHandle = null;
    }

    delete self.requests.in[id];
    self.pending.in--;

    return req;
};

Operations.prototype.clear = function clear() {
    var self = this;

    var now = self.timers.now();
    var inReqKeys = Object.keys(self.requests.in);
    var outReqKeys = Object.keys(self.requests.out);

    for (var i = 0; i < inReqKeys.length; i++) {
        self.popInReq(inReqKeys[i]);
    }
    for (var j = 0; j < outReqKeys.length; j++) {
        self.popOutReq(outReqKeys[j]);

        var tombstone = self.requests.out[outReqKeys[j]];
        if (tombstone.timeHeapHandle) {
            tombstone.timeHeapHandle.cancel();
        }
        tombstone.destroy(now);
    }
};

Operations.prototype.destroy = function destroy() {
    var self = this;

    self.destroyed = true;
};

Operations.prototype.sanitySweep = function sanitySweep() {
    var self = this;

    self._sweepOps(self.requests.in, 'in');
    self._sweepOps(self.requests.out, 'out');
};

Operations.prototype._sweepOps = function _sweepOps(ops, direction) {
    var self = this;

    var now = self.timers.now();
    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var op = ops[id];
        if (op === undefined) {
            self.logger.warn('unexpected undefined operation', {
                direction: direction,
                id: id
            });
        } else if (op.timedOut) {
            self.logger.warn('lingering timed-out operation', {
                direction: direction,
                id: id
            });

            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        } else if (op.isTombstone) {
            var heap = self.connection.channel.timeHeap;
            var expireTime = op.time + op.timeout;

            if (!op.operations) {
                self.logger.warn('zombie tombstone', op.extendLogInfo({
                    direction: direction,
                    opKey: id
                }));
                delete ops[id];
                op.operations = null;
                op.timeHeapHandle.cancel();
                op.timeHeapHandle = null;
            } else if (expireTime < now && heap.lastRun > expireTime) {
                self.logger.warn('stale tombstone', op.extendLogInfo({
                    direction: direction,
                    opKey: id,
                    now: now,
                    staleDelta: op.time + op.timeout - now,
                    expireTime: expireTime,
                    heapLastRun: heap.lastRun
                }));
                delete ops[id];
                op.operations = null;
                op.timeHeapHandle.cancel();
                op.timeHeapHandle = null;
            }
        }
    }
};
<|endoftext|>


<commit_before>// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require('./utils');
const path = require('path');
const fs = require('fs').promises;
const os = require('os');
const Minifier = require('../lib/minifier');

describe('Minifier', function () {
    let minifier;
    let testDir;

    before(async function () {
        testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'minifier-tests-'));

        minifier = new Minifier({
            src: path.join(__dirname, 'fixtures', 'basic-cards'),
            dest: testDir
        });
    });

    after(async function () {
        await fs.rmdir(testDir, {recursive: true});
    });

    describe('getMatchingFiles expands globs correctly', function () {
        it('star glob e.g. css/*.css', async function () {
            let result = await minifier.getMatchingFiles('css/*.css');

            result.should.be.an.Array().with.lengthOf(3);
            result[0].should.eql('test/fixtures/basic-cards/css/bookmark.css');
            result[1].should.eql('test/fixtures/basic-cards/css/empty.css');
            result[2].should.eql('test/fixtures/basic-cards/css/gallery.css');
        });

        it('reverse match glob e.g. css/!(bookmark).css', async function () {
            let result = await minifier.getMatchingFiles('css/!(bookmark).css');

            result.should.be.an.Array().with.lengthOf(2);
            result[0].should.eql('test/fixtures/basic-cards/css/empty.css');
            result[1].should.eql('test/fixtures/basic-cards/css/gallery.css');
        });
        it('reverse match glob e.g. css/!(bookmark|gallery).css', async function () {
            let result = await minifier.getMatchingFiles('css/!(bookmark|gallery).css');

            result.should.be.an.Array().with.lengthOf(1);
            result[0].should.eql('test/fixtures/basic-cards/css/empty.css');
        });
    });

    describe('Minify', function () {
        it('single type, single file', async function () {
            let result = await minifier.minify({
                'card.min.js': 'js/*.js'
            });
            result.should.be.an.Array().with.lengthOf(1);
        });

        it('single type, multi file', async function () {
            let result = await minifier.minify({
                'card.min.css': 'css/*.css'
            });
            result.should.be.an.Array().with.lengthOf(1);
        });

        it('both css and js types + multiple files', async function () {
            let result = await minifier.minify({
                'card.min.js': 'js/*.js',
                'card.min.css': 'css/*.css'
            });

            result.should.be.an.Array().with.lengthOf(2);
        });
    });

    describe('Bad inputs', function () {
        it('cannot create a minifier without src and dest', function () {
            (function noObject(){
                new Minifier();
            }).should.throw();

            (function emptyObject() {
                new Minifier({});
            }).should.throw();

            (function missingSrc() {
                new Minifier({dest: 'a'});
            }).should.throw();

            (function missingDest() {
                new Minifier({src: 'a'});
            }).should.throw();
        });

        it('can only handle css and js files', async function () {
            try {
                await minifier.minify({
                    'card.min.ts': 'js/*.ts'
                });
                should.fail(minifier, 'Should have errored');
            } catch (err) {
                should.exist(err);
                err.errorType.should.eql('IncorrectUsageError');
                err.message.should.match(/Unexpected destination/);
            }
        });

        it('can handle missing files and folders gracefully', async function () {
            try {
                await minifier.minify({
                    'card.min.ts': 'ts/*.ts',
                    'card.min.js': 'js/fake.js'
                });
                should.fail(minifier, 'Should have errored');
            } catch (err) {
                should.exist(err);
                err.errorType.should.eql('IncorrectUsageError');
                err.message.should.match(/Unable to read/);
            }
        });

        it('can minify empty js correctly to no result', async function () {
            let result = await minifier.minify({
                'card.min.js': 'js/empty.js'
            });

            result.should.be.an.Array().with.lengthOf(0);
        });

        it('can minify empty css correctly to no result', async function () {
            let result = await minifier.minify({
                'card.min.css': 'css/empty.css'
            });

            result.should.be.an.Array().with.lengthOf(0);
        });
    });
});
<commit_msg>Updated import path for minifier tests<commit_after>// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require('./utils');
const path = require('path');
const fs = require('fs').promises;
const os = require('os');
const Minifier = require('../');

describe('Minifier', function () {
    let minifier;
    let testDir;

    before(async function () {
        testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'minifier-tests-'));

        minifier = new Minifier({
            src: path.join(__dirname, 'fixtures', 'basic-cards'),
            dest: testDir
        });
    });

    after(async function () {
        await fs.rmdir(testDir, {recursive: true});
    });

    describe('getMatchingFiles expands globs correctly', function () {
        it('star glob e.g. css/*.css', async function () {
            let result = await minifier.getMatchingFiles('css/*.css');

            result.should.be.an.Array().with.lengthOf(3);
            result[0].should.eql('test/fixtures/basic-cards/css/bookmark.css');
            result[1].should.eql('test/fixtures/basic-cards/css/empty.css');
            result[2].should.eql('test/fixtures/basic-cards/css/gallery.css');
        });

        it('reverse match glob e.g. css/!(bookmark).css', async function () {
            let result = await minifier.getMatchingFiles('css/!(bookmark).css');

            result.should.be.an.Array().with.lengthOf(2);
            result[0].should.eql('test/fixtures/basic-cards/css/empty.css');
            result[1].should.eql('test/fixtures/basic-cards/css/gallery.css');
        });
        it('reverse match glob e.g. css/!(bookmark|gallery).css', async function () {
            let result = await minifier.getMatchingFiles('css/!(bookmark|gallery).css');

            result.should.be.an.Array().with.lengthOf(1);
            result[0].should.eql('test/fixtures/basic-cards/css/empty.css');
        });
    });

    describe('Minify', function () {
        it('single type, single file', async function () {
            let result = await minifier.minify({
                'card.min.js': 'js/*.js'
            });
            result.should.be.an.Array().with.lengthOf(1);
        });

        it('single type, multi file', async function () {
            let result = await minifier.minify({
                'card.min.css': 'css/*.css'
            });
            result.should.be.an.Array().with.lengthOf(1);
        });

        it('both css and js types + multiple files', async function () {
            let result = await minifier.minify({
                'card.min.js': 'js/*.js',
                'card.min.css': 'css/*.css'
            });

            result.should.be.an.Array().with.lengthOf(2);
        });
    });

    describe('Bad inputs', function () {
        it('cannot create a minifier without src and dest', function () {
            (function noObject(){
                new Minifier();
            }).should.throw();

            (function emptyObject() {
                new Minifier({});
            }).should.throw();

            (function missingSrc() {
                new Minifier({dest: 'a'});
            }).should.throw();

            (function missingDest() {
                new Minifier({src: 'a'});
            }).should.throw();
        });

        it('can only handle css and js files', async function () {
            try {
                await minifier.minify({
                    'card.min.ts': 'js/*.ts'
                });
                should.fail(minifier, 'Should have errored');
            } catch (err) {
                should.exist(err);
                err.errorType.should.eql('IncorrectUsageError');
                err.message.should.match(/Unexpected destination/);
            }
        });

        it('can handle missing files and folders gracefully', async function () {
            try {
                await minifier.minify({
                    'card.min.ts': 'ts/*.ts',
                    'card.min.js': 'js/fake.js'
                });
                should.fail(minifier, 'Should have errored');
            } catch (err) {
                should.exist(err);
                err.errorType.should.eql('IncorrectUsageError');
                err.message.should.match(/Unable to read/);
            }
        });

        it('can minify empty js correctly to no result', async function () {
            let result = await minifier.minify({
                'card.min.js': 'js/empty.js'
            });

            result.should.be.an.Array().with.lengthOf(0);
        });

        it('can minify empty css correctly to no result', async function () {
            let result = await minifier.minify({
                'card.min.css': 'css/empty.css'
            });

            result.should.be.an.Array().with.lengthOf(0);
        });
    });
});
<|endoftext|>


<commit_before>INDEXED_FIELDS_AND_WEIGHTS = {
  'items':  {
      'title': 5,
      'content': 1
  }
}

STEMMING = 'porter'; // doesn't do anything yet
TOKENIZING = 'standard';// doesn't do anything yet

function mft_search() {
  // what hsould this do?
}

function mft_filtered(coll_name, query_string, require_all) {
  var query_terms = mft_process_query_string(query_string);
}

function mft_process_query_string(query_string) {
  return mft_stem_and_tokenize(query_string); // maybe tokenizing should be different for queries?
}

function mft_index_all(coll_name) {
  var cur = db.coll_name.find();
  cur.forEach(function(x) { mft_index_single_record(coll_name, x); });
}

function mft_index_single_record(coll_name, record) {
  for (var field in INDEXED_FIELDS_AND_WEIGHTS[coll_name]) {
    extract_and_store_field_tokens(coll_name, record, field);
  }
}

function mft_index_single_record_from_id(coll_name, record_id) {
  var rec = db[coll_name].find_one({'_id': record_id});
  mft_index_single_record(coll_name, rec);
}

function mft_extract_and_store_field_tokens(coll_name, record, field) {
  var contents = record[field];
  if (!contents) { // eg the field doesn't exist on this particular record, we silently fail
    return;
  }
  var processed_contents = mft_stem_and_tokenize(contents);
//  mft_store_postings(coll_name, record, field, processed_contents); // we want it in postings list -- or maybe not - will leave out for now
  record['_extracted_' + field] = processed_contents; // but also in the record, for various reasons.
}

function mft_stem_and_tokenize(field_contents) {
  return field_contents.split(' '); // TODO: stemming and smart tokenising (should look at the config vars above)
}

function mft_store_postings(coll_name, record_id, field, processed_contents) {
  // we don't need the postings list to do the search because of mongo magic
  // however we do want to know how many documents a term appears in
  // and to make this easy to update, it  makes sense to use a postings list as well
  var postings_coll = get_postings_collection(coll_name);
  for (var i = 0; i < processed_contents.length; i++) {
    var term = processed_contents[i];
    store_postings_term(postings_coll, record_id, term)
  }
}

function mft_store_postings_term(postings_collection, record_id, term) {
  // append to the array if it doesn't already exist
  postings_collection.upsert({'term': term}, {'term': term, $push: {'document_ids': record_id}});
  // this is assuming we don't need to do duplicate checking, which will do for now.
}

function mft_get_postings_collection(coll_name) { // we could also split this out on field type but I don't think the use case for it is strong enough
  var coll = db['_postings_' + coll_name];
  coll.ensureIndex({'term':1});
  return coll
}
  <commit_msg>added some comment about how the search thing is goingto work<commit_after>INDEXED_FIELDS_AND_WEIGHTS = {
  'items':  {
      'title': 5,
      'content': 1
  }
}

STEMMING = 'porter'; // doesn't do anything yet
TOKENIZING = 'standard';// doesn't do anything yet

function mft_search(coll_name, query_obj) {
  // check for $search member on query_obj
  // if it doesn't exist, pass through to regular .find
  // if it does, parse the ft query string, and add the appropriate filter
  // clause to the non-ft-search components, execute that, then
  // score every remaining document, and put those sorted IDs and scores in a record in
  // a private collection (hashed by the whole query obj, which we can check next time around)
  // then iterate through the IDs and scores and return the corrpesponding records with the IDs
  // attached to them, in a way that emulates a cursor object.
  // what hsould this do?
}

function mft_filtered(coll_name, query_string, require_all) {
  var query_terms = mft_process_query_string(query_string);
}

function mft_process_query_string(query_string) {
  return mft_stem_and_tokenize(query_string); // maybe tokenizing should be different for queries?
}

function mft_index_all(coll_name) {
  var cur = db.coll_name.find();
  cur.forEach(function(x) { mft_index_single_record(coll_name, x); });
}

function mft_index_single_record(coll_name, record) {
  for (var field in INDEXED_FIELDS_AND_WEIGHTS[coll_name]) {
    extract_and_store_field_tokens(coll_name, record, field);
  }
}

function mft_index_single_record_from_id(coll_name, record_id) {
  var rec = db[coll_name].find_one({'_id': record_id});
  mft_index_single_record(coll_name, rec);
}

function mft_extract_and_store_field_tokens(coll_name, record, field) {
  var contents = record[field];
  if (!contents) { // eg the field doesn't exist on this particular record, we silently fail
    return;
  }
  var processed_contents = mft_stem_and_tokenize(contents);
//  mft_store_postings(coll_name, record, field, processed_contents); // we want it in postings list -- or maybe not - will leave out for now
  record['_extracted_' + field] = processed_contents; // but also in the record, for various reasons.
}

function mft_stem_and_tokenize(field_contents) {
  return field_contents.split(' '); // TODO: stemming and smart tokenising (should look at the config vars above)
}

function mft_store_postings(coll_name, record_id, field, processed_contents) {
  // we don't need the postings list to do the search because of mongo magic
  // however we do want to know how many documents a term appears in
  // and to make this easy to update, it  makes sense to use a postings list as well
  var postings_coll = get_postings_collection(coll_name);
  for (var i = 0; i < processed_contents.length; i++) {
    var term = processed_contents[i];
    store_postings_term(postings_coll, record_id, term)
  }
}

function mft_store_postings_term(postings_collection, record_id, term) {
  // append to the array if it doesn't already exist
  postings_collection.upsert({'term': term}, {'term': term, $push: {'document_ids': record_id}});
  // this is assuming we don't need to do duplicate checking, which will do for now.
}

function mft_get_postings_collection(coll_name) { // we could also split this out on field type but I don't think the use case for it is strong enough
  var coll = db['_postings_' + coll_name];
  coll.ensureIndex({'term':1});
  return coll
}
  <|endoftext|>


<commit_before>/**
* @author Virtuosi Media
* @link http://www.virtuosimedia.com
* @version 1.0
* @copyright Copyright (c) 2012, Virtuosi Media
* @license: MIT License
* @description: Creates mobile menus
* Requirements: MooTools 1.4 Core - See http://mootools.net
*/
var MobileMenu = new Class({

	Implements: [Events, Options],

	/**
	 * @param string - selectors - The selectors for the menu
	 */
	initialize: function(selectors){
		this.menus = $$(selectors);
		var self = this;

		if (self.isMobile()){
			self.createMobileMenus();
		}
		window.addEvent('resize', function(){
			if (self.isMobile()){
				self.adjustFloatingMenus(true);
				if ($$('.mobileMenuTrigger').length == 0){self.createMobileMenus();}
			} else {
				self.adjustFloatingMenus(false);
				self.resetMobileMenus();
			}
		});
	},

	isMobile: function(){
		if (window.matchMedia){
			return window.matchMedia('(max-device-width: 960px)').matches;
		} else {
			return screen.width <= 960;
		}
	},

	createMobileMenus: function(){
		var self = this;
		var buttonContainer = new Element('ul', {id: 'mobileButtonContainer', 'class': 'buttonGroup', styles: {float: 'right'}});
		Array.each(this.menus, function(menu, index){
			menu.setStyles({overflow: 'hidden', height: 0});
			menu.getParent('[class*=col]').setStyle('margin-bottom', 0);
			var text = (menu.hasData('text')) ? menu.getData('text') : 'Menu';
			var button = new Element('span', {
				'class': 'contrastButton mobileMenuTrigger',
				html: text,
				events: {
					click: function(){
						if (menu.getStyle('height') == '0px'){
							self.menus.setStyle('height', 0);
							$$('.mobileMenuTrigger').setData('state', null);
							menu.setStyle('height', 'auto');
							this.setData('state', 'active');
							if ($$('.navbarContainer')[0].hasClass('fixed')){
								$$('.navbarContainer')[0].setStyle('position', 'static');
							}
						} else {
							menu.setStyle('height', 0);
							this.setData('state', null);
							self.resetFixedMenus();
						}
					}
				}
			})
			var li = new Element('li').adopt(button).inject(buttonContainer);
		});
		buttonContainer.inject(this.menus[0], 'before');
	},

	resetFixedMenus: function(){
		if (($$('.navbarContainer').length > 0) && ($$('.navbarContainer')[0].hasClass('fixed'))){
			$$('.navbarContainer')[0].setStyle('position', 'fixed');
		}
	},

	resetMobileMenus: function(){
		this.resetFixedMenus();
		this.menus.setStyles({height: 'auto', overflow: null});
		if ($('mobileButtonContainer')) {$('mobileButtonContainer').dispose();}
	},

	adjustFloatingMenus: function(mobile){
		var floaters = (mobile)
			? $$('.floatingNav [class*=col]').setStyle('padding', 0)
			: $$('.floatingNav [class*=col]').setStyle('padding', '0 1em');
	}
});<commit_msg>Fixed mobile menu media query so that it works on the desktop, closes #148<commit_after>/**
* @author Virtuosi Media
* @link http://www.virtuosimedia.com
* @version 1.0
* @copyright Copyright (c) 2012, Virtuosi Media
* @license: MIT License
* @description: Creates mobile menus
* Requirements: MooTools 1.4 Core - See http://mootools.net
*/
var MobileMenu = new Class({

	Implements: [Events, Options],

	/**
	 * @param string - selectors - The selectors for the menu
	 */
	initialize: function(selectors){
		this.menus = $$(selectors);
		var self = this;

		if (self.isMobile()){
			self.createMobileMenus();
		}
		window.addEvent('resize', function(){
			if (self.isMobile()){
				self.adjustFloatingMenus(true);
				if ($$('.mobileMenuTrigger').length == 0){self.createMobileMenus();}
			} else {
				self.adjustFloatingMenus(false);
				self.resetMobileMenus();
			}
		});
	},

	isMobile: function(){
		if (window.matchMedia){
			return window.matchMedia('screen and (max-width: 767px), screen and (max-device-width: 767px)').matches;
		} else {
			return screen.width <= 960;
		}
	},

	createMobileMenus: function(){
		var self = this;
		var buttonContainer = new Element('ul', {id: 'mobileButtonContainer', 'class': 'buttonGroup', styles: {float: 'right'}});
		Array.each(this.menus, function(menu, index){
			menu.setStyles({overflow: 'hidden', height: 0});
			menu.getParent('[class*=col]').setStyle('margin-bottom', 0);
			var text = (menu.hasData('text')) ? menu.getData('text') : 'Menu';
			var button = new Element('span', {
				'class': 'contrastButton mobileMenuTrigger',
				html: text,
				events: {
					click: function(){
						if (menu.getStyle('height') == '0px'){
							self.menus.setStyle('height', 0);
							$$('.mobileMenuTrigger').setData('state', null);
							menu.setStyle('height', 'auto');
							this.setData('state', 'active');
							if ($$('.navbarContainer')[0].hasClass('fixed')){
								$$('.navbarContainer')[0].setStyle('position', 'static');
							}
						} else {
							menu.setStyle('height', 0);
							this.setData('state', null);
							self.resetFixedMenus();
						}
					}
				}
			})
			var li = new Element('li').adopt(button).inject(buttonContainer);
		});
		buttonContainer.inject(this.menus[0], 'before');
	},

	resetFixedMenus: function(){
		if (($$('.navbarContainer').length > 0) && ($$('.navbarContainer')[0].hasClass('fixed'))){
			$$('.navbarContainer')[0].setStyle('position', 'fixed');
		}
	},

	resetMobileMenus: function(){
		this.resetFixedMenus();
		this.menus.setStyles({height: 'auto', overflow: null});
		if ($('mobileButtonContainer')) {$('mobileButtonContainer').dispose();}
	},

	adjustFloatingMenus: function(mobile){
		var floaters = (mobile)
			? $$('.floatingNav [class*=col]').setStyle('padding', 0)
			: $$('.floatingNav [class*=col]').setStyle('padding', '0 1em');
	}
});<|endoftext|>


<commit_before>var Funnel = require('broccoli-funnel');
var Rollup = require('broccoli-rollup');
var MergeTrees = require('broccoli-merge-trees');

module.exports = function(/* defaults */) {
  var lib = new Funnel('lib/simple-html-tokenizer', {
    include: ['**/*.js'],
    destDir: '/'
  });

  var bundled = new Rollup(lib, {
    rollup: {
      entry: 'index.js',

      sourceMap: true,
      dest: 'simple-html-tokenizer.js',
      format: 'umd',
      moduleName: 'HTML5Tokenizer'
    }
  });

  var tests = new Funnel('test', {
    files: ['index.html', 'tokenizer-tests.js'],
    destDir: '/tests'
  });

  var qunit = new Funnel('node_modules/qunitjs', {
    srcDir: 'qunit',
    files: ['qunit.js', 'qunit.css'],
    destDir: '/tests'
  });

  return new MergeTrees([bundled, tests, qunit]);
};
<commit_msg>build: Extract "testSupport" tree<commit_after>var Funnel = require('broccoli-funnel');
var Rollup = require('broccoli-rollup');
var MergeTrees = require('broccoli-merge-trees');

module.exports = function(/* defaults */) {
  var lib = new Funnel('lib/simple-html-tokenizer', {
    include: ['**/*.js'],
    destDir: '/'
  });

  var bundled = new Rollup(lib, {
    rollup: {
      entry: 'index.js',

      sourceMap: true,
      dest: 'simple-html-tokenizer.js',
      format: 'umd',
      moduleName: 'HTML5Tokenizer'
    }
  });

  var tests = new Funnel('test', {
    files: ['tokenizer-tests.js'],
    destDir: '/tests'
  });

  var testSupport = new Funnel('test', {
    files: ['index.html'],
    destDir: '/tests'
  });

  var qunit = new Funnel('node_modules/qunitjs', {
    srcDir: 'qunit',
    files: ['qunit.js', 'qunit.css'],
    destDir: '/tests'
  });

  return new MergeTrees([bundled, tests, testSupport, qunit]);
};
<|endoftext|>