<commit_before>import {cloneRepository, buildRepositoryWithPipeline} from './helpers';
import {GitError} from '../lib/git-shell-out-strategy';

describe('getRepoPipelineManager()', function() {

  let atomEnv, workspace, notificationManager, repo, pipelineManager, confirm;

  const getPipeline = (pm, actionName) => {
    const actionKey = pm.actionKeys[actionName];
    return pm.getPipeline(actionKey);
  };

  const buildRepo = (workdir, override = {}) => {
    const option = {
      confirm,
      notificationManager,
      workspace,
      ...override,
    };
    return buildRepositoryWithPipeline(workdir, option);
  };

  const gitErrorStub = (stdErr = '', stdOut = '') => {
    return sinon.stub().throws(() => {
      const err = new GitError();
      err.stdErr = stdErr;
      err.stdOut = stdOut;
      return err;
    });
  };

  beforeEach(async function() {
    atomEnv = global.buildAtomEnvironment();
    workspace = atomEnv.workspace;
    notificationManager = atomEnv.notifications;
    confirm = sinon.stub(atomEnv, 'confirm');

    const workdir = await cloneRepository('multiple-commits');
    repo = await buildRepo(workdir);
    pipelineManager = repo.pipelineManager;
  });

  afterEach(function() {
    atomEnv.destroy();
  });

  it('has all the action pipelines', function() {
    const expectedActions = ['PUSH', 'PULL', 'FETCH', 'COMMIT', 'CHECKOUT', 'ADDREMOTE'];
    for (const actionName of expectedActions) {
      assert.ok(getPipeline(pipelineManager, actionName));
    }
  });

  describe('PUSH pipeline', function() {

    it('confirm-force-push', function() {
      it('before confirming', function() {
        const pushPipeline = getPipeline(pipelineManager, 'PUSH');
        const pushStub = sinon.stub();
        sinon.spy(notificationManager, 'addError');
        pushPipeline.run(pushStub, repo, '', {force: true});
        assert.isTrue(confirm.calledWith({
          message: 'Are you sure you want to force push?',
          detailedMessage: 'This operation could result in losing data on the remote.',
          buttons: ['Force Push', 'Cancel'],
        }));
        assert.isTrue(pushStub.called());
      });

      it('after confirming', async function() {
        const nWorkdir = await cloneRepository('multiple-commits');
        const confirmStub = sinon.stub(atomEnv, 'confirm').return(0);
        const nRepo = buildRepo(nWorkdir, {confirm: confirmStub});
        const pushPipeline = getPipeline(nRepo.pipelineManager, 'PUSH');
        const pushStub = sinon.stub();
        sinon.spy(notificationManager, 'addError');

        pushPipeline.run(pushStub, repo, '', {force: true});
        assert.isFalse(confirm.called);
        assert.isFalse(pushStub.called);
      });
    });

    it('set-push-in-progress', async function() {
      const pushPipeline = getPipeline(pipelineManager, 'PUSH');
      const pushStub = sinon.stub().callsFake(() => {
        assert.isTrue(repo.getOperationStates().isPushInProgress());
        return Promise.resolve();
      });
      pushPipeline.run(pushStub, repo, '', {});
      assert.isTrue(pushStub.called);
      await assert.async.isFalse(repo.getOperationStates().isPushInProgress());
    });

    it('failed-to-push-error', function() {
      const pushPipeline = getPipeline(pipelineManager, 'PUSH');
      sinon.spy(notificationManager, 'addError');

      pushPipeline.run(gitErrorStub('rejected failed to push'), repo, '', {});
      assert.isTrue(notificationManager.addError.calledWithMatch('Push rejected', {dismissable: true}));

      pushPipeline.run(gitErrorStub('something else'), repo, '', {});
      assert.isTrue(notificationManager.addError.calledWithMatch('Unable to push', {dismissable: true}));
    });
  });

  describe('COMMIT pipeline', function() {
    it('confirm-commit', function() {

    });

    it('clean-up-disk-commit-msg', function() {

    });

    it('set-commit-in-progress', function() {

    });

    it('failed-to-commit-error', function() {

    });
  });

  describe('ADDREMOTE pipeline', function() {
    it('failed-to-add-remote', function() {

    });
  });
});
<commit_msg>pull pipeline tests<commit_after>import {cloneRepository, buildRepositoryWithPipeline} from './helpers';
import {GitError} from '../lib/git-shell-out-strategy';

describe('getRepoPipelineManager()', function() {

  let atomEnv, workspace, notificationManager, repo, pipelineManager, confirm;

  const getPipeline = (pm, actionName) => {
    const actionKey = pm.actionKeys[actionName];
    return pm.getPipeline(actionKey);
  };

  const buildRepo = (workdir, override = {}) => {
    const option = {
      confirm,
      notificationManager,
      workspace,
      ...override,
    };
    return buildRepositoryWithPipeline(workdir, option);
  };

  const gitErrorStub = (stdErr = '', stdOut = '') => {
    return sinon.stub().throws(() => {
      const err = new GitError();
      err.stdErr = stdErr;
      err.stdOut = stdOut;
      return err;
    });
  };

  beforeEach(async function() {
    atomEnv = global.buildAtomEnvironment();
    workspace = atomEnv.workspace;
    notificationManager = atomEnv.notifications;
    confirm = sinon.stub(atomEnv, 'confirm');

    const workdir = await cloneRepository('multiple-commits');
    repo = await buildRepo(workdir);
    pipelineManager = repo.pipelineManager;
  });

  afterEach(function() {
    atomEnv.destroy();
  });

  it('has all the action pipelines', function() {
    const expectedActions = ['PUSH', 'PULL', 'FETCH', 'COMMIT', 'CHECKOUT', 'ADDREMOTE'];
    for (const actionName of expectedActions) {
      assert.ok(getPipeline(pipelineManager, actionName));
    }
  });

  describe('PUSH pipeline', function() {

    it('confirm-force-push', function() {
      it('before confirming', function() {
        const pushPipeline = getPipeline(pipelineManager, 'PUSH');
        const pushStub = sinon.stub();
        sinon.spy(notificationManager, 'addError');
        pushPipeline.run(pushStub, repo, '', {force: true});
        assert.isTrue(confirm.calledWith({
          message: 'Are you sure you want to force push?',
          detailedMessage: 'This operation could result in losing data on the remote.',
          buttons: ['Force Push', 'Cancel'],
        }));
        assert.isTrue(pushStub.called());
      });

      it('after confirming', async function() {
        const nWorkdir = await cloneRepository('multiple-commits');
        const confirmStub = sinon.stub(atomEnv, 'confirm').return(0);
        const nRepo = buildRepo(nWorkdir, {confirm: confirmStub});
        const pushPipeline = getPipeline(nRepo.pipelineManager, 'PUSH');
        const pushStub = sinon.stub();
        sinon.spy(notificationManager, 'addError');

        pushPipeline.run(pushStub, repo, '', {force: true});
        assert.isFalse(confirm.called);
        assert.isFalse(pushStub.called);
      });
    });

    it('set-push-in-progress', async function() {
      const pushPipeline = getPipeline(pipelineManager, 'PUSH');
      const pushStub = sinon.stub().callsFake(() => {
        assert.isTrue(repo.getOperationStates().isPushInProgress());
        return Promise.resolve();
      });
      pushPipeline.run(pushStub, repo, '', {});
      assert.isTrue(pushStub.called);
      await assert.async.isFalse(repo.getOperationStates().isPushInProgress());
    });

    it('failed-to-push-error', function() {
      const pushPipeline = getPipeline(pipelineManager, 'PUSH');
      sinon.spy(notificationManager, 'addError');

      pushPipeline.run(gitErrorStub('rejected failed to push'), repo, '', {});
      assert.isTrue(notificationManager.addError.calledWithMatch('Push rejected', {dismissable: true}));

      pushPipeline.run(gitErrorStub('something else'), repo, '', {});
      assert.isTrue(notificationManager.addError.calledWithMatch('Unable to push', {dismissable: true}));
    });
  });

  describe('PULL pipeline', function() {
    it('set-pull-in-progress', async function() {
      const pull = getPipeline(pipelineManager, 'PULL');
      const pullStub = sinon.stub().callsFake(() => {
        assert.isTrue(repo.getOperationStates().isPullInProgress());
        return Promise.resolve();
      });
      pull.run(pullStub, repo, '', {});
      assert.isTrue(pullStub.called);
      await assert.async.isFalse(repo.getOperationStates().isPullInProgress());
    });

    it('failed-to-pull-error', function() {
      const pullPipeline = getPipeline(pipelineManager, 'PULL');
      sinon.spy(notificationManager, 'addError');
      sinon.spy(notificationManager, 'addWarning');

      pullPipeline.run(gitErrorStub('error: Your local changes to the following files would be overwritten by merge'), repo, '', {});
      assert.isTrue(notificationManager.addError.calledWithMatch('Pull aborted', {dismissable: true}));

      pullPipeline.run(gitErrorStub('', 'Automatic merge failed; fix conflicts and then commit the result.'), repo, '', {});
      // console.log(notificationManager.addError.args)
      assert.isTrue(notificationManager.addWarning.calledWithMatch('Merge conflicts', {dismissable: true}));

      pullPipeline.run(gitErrorStub('fatal: Not possible to fast-forward, aborting.'), repo, '', {});
      assert.isTrue(notificationManager.addWarning.calledWithMatch('Unmerged changes', {dismissable: true}));

      pullPipeline.run(gitErrorStub('something else'), repo, '', {});
      assert.isTrue(notificationManager.addError.calledWithMatch('Unable to pull', {dismissable: true}));
    });
  });

  // describe('FETCH pipeline', function() {
  //   it('set-fetch-in-progress', function() {
  //
  //   });
  //
  //   it('failed-to-fetch-error', function() {
  //
  //   });
  // });
  //
  // describe('CHECKOUT pipeline', function() {
  //   it('set-checkout-in-progress', function() {
  //
  //   });
  //
  //   it('failed-to-checkout-error', function() {
  //
  //   });
  // });
  //
  // describe('COMMIT pipeline', function() {
  //   it('confirm-commit', function() {
  //
  //   });
  //
  //   it('clean-up-disk-commit-msg', function() {
  //
  //   });
  //
  //   it('set-commit-in-progress', function() {
  //
  //   });
  //
  //   it('failed-to-commit-error', function() {
  //
  //   });
  // });
  //
  // describe('ADDREMOTE pipeline', function() {
  //   it('failed-to-add-remote', function() {
  //
  //   });
  // });
});
<|endoftext|>


<commit_before>var _ = require('underscore');

// Ordered from most to least specific.
var quotePatterns = [
    /^On[\s\S]*?wrote[\s\S]*(?:^>).*/mg
];

/**
 * Attempts to extract only the message from the email.
 * @param email
 */
exports.extractEmailText = function(email) {
    for (var i = 0; i < quotePatterns.length; i++) {
        if (quotePatterns[i].test(email)) {
            return email.replace(quotePatterns[i], '').trim();
        }
    }
}

<commit_msg>Remove now-redundant email helper library<commit_after><|endoftext|>


<commit_before>const token_manager = require('../app/token_manager');
const jwt = require('jsonwebtoken');

describe("Token Manager", () => {
	describe(">getSocketToken", () => {
		it("should call jwt.sign with a payload of a user id and role when the user is provided", () => {
			expect(token_manager.getSocketToken).toBeDefined();

			const spy_sign = spyOn(jwt, 'sign').and.returnValue('this_is_a_test_token');

			process.env.JWT_SECRET = 'TEST_SECRET';

			expect(token_manager.getSocketToken({'_id': 'test_id', 'role': 'test_user'})).toEqual('this_is_a_test_token');
			expect(jwt.sign).toHaveBeenCalled();
			expect(spy_sign.calls.count()).toEqual(1);
			expect(spy_sign.calls.argsFor(0).length).toEqual(3);
			expect(spy_sign.calls.argsFor(0)[0]).toEqual({
				uid: 'test_id',
				role: 'test_user'
			});
			expect(spy_sign.calls.argsFor(0)[1]).toEqual('TEST_SECRET');
			expect(spy_sign.calls.argsFor(0)[2]).toEqual({
				expiresIn: 60 * 10
			});
		});

		it("should call jwt.sign with a payload of role=guest when the user is not provided",() => {
			expect(token_manager.getSocketToken).toBeDefined();

			const spy_sign = spyOn(jwt, 'sign').and.returnValue('this_is_a_test_token2');

			process.env.JWT_SECRET = 'TEST_SECRET2';

			expect(token_manager.getSocketToken()).toEqual('this_is_a_test_token2');
			expect(jwt.sign).toHaveBeenCalled();
			expect(spy_sign.calls.count()).toEqual(1);
			expect(spy_sign.calls.argsFor(0).length).toEqual(3);
			expect(spy_sign.calls.argsFor(0)[0]).toEqual({
				role: 'guest'
			});
			expect(spy_sign.calls.argsFor(0)[1]).toEqual('TEST_SECRET2');
			expect(spy_sign.calls.argsFor(0)[2]).toEqual({
				expiresIn: 60 * 10
			});
		});
	});
});
<commit_msg>Add test for verifyToken()<commit_after>const token_manager = require('../app/token_manager');
const jwt = require('jsonwebtoken');

describe("Token Manager", () => {
	describe(">getSocketToken", () => {
		it("should call jwt.sign with a payload of a user id and role when the user is provided", () => {
			expect(token_manager.getSocketToken).toBeDefined();

			const spy_sign = spyOn(jwt, 'sign').and.returnValue('this_is_a_test_token');

			process.env.JWT_SECRET = 'TEST_SECRET';

			expect(token_manager.getSocketToken({'_id': 'test_id', 'role': 'test_user'})).toEqual('this_is_a_test_token');
			expect(jwt.sign).toHaveBeenCalled();
			expect(spy_sign.calls.count()).toEqual(1);
			expect(spy_sign.calls.argsFor(0).length).toEqual(3);
			expect(spy_sign.calls.argsFor(0)[0]).toEqual({
				uid: 'test_id',
				role: 'test_user'
			});
			expect(spy_sign.calls.argsFor(0)[1]).toEqual('TEST_SECRET');
			expect(spy_sign.calls.argsFor(0)[2]).toEqual({
				expiresIn: 60 * 10
			});
		});

		it("should call jwt.sign with a payload of role=guest when the user is not provided",() => {
			expect(token_manager.getSocketToken).toBeDefined();

			const spy_sign = spyOn(jwt, 'sign').and.returnValue('this_is_a_test_token2');

			process.env.JWT_SECRET = 'TEST_SECRET2';

			expect(token_manager.getSocketToken()).toEqual('this_is_a_test_token2');
			expect(jwt.sign).toHaveBeenCalled();
			expect(spy_sign.calls.count()).toEqual(1);
			expect(spy_sign.calls.argsFor(0).length).toEqual(3);
			expect(spy_sign.calls.argsFor(0)[0]).toEqual({
				role: 'guest'
			});
			expect(spy_sign.calls.argsFor(0)[1]).toEqual('TEST_SECRET2');
			expect(spy_sign.calls.argsFor(0)[2]).toEqual({
				expiresIn: 60 * 10
			});
		});
	});

	describe(">verifyToken", () => {
		it("should call jwt.verify with the token, secret and callback", () => {
			expect(token_manager.verifyToken).toBeDefined();

			const spy_verify = spyOn(jwt, 'verify').and.returnValue('DUMMY_RETURN');

			process.env.JWT_SECRET = 'TEST_SECRET';

			expect(token_manager.verifyToken('TEST_TOKEN', 'DUMMY_CALLBACK_VALUE'));
			expect(jwt.verify).toHaveBeenCalled();
			expect(spy_verify.calls.count()).toEqual(1);
			expect(spy_verify.calls.argsFor(0).length).toEqual(3);
			expect(spy_verify.calls.argsFor(0)[0]).toEqual("TEST_TOKEN");
			expect(spy_verify.calls.argsFor(0)[1]).toEqual('TEST_SECRET');
			expect(spy_verify.calls.argsFor(0)[2]).toEqual("DUMMY_CALLBACK_VALUE");
		})
	})
});
<|endoftext|>


<commit_before>import '../src'
import bufferEqual from 'buffer-equal'
import convert from 'convert-source-map'
import gulp from 'gulp'
import timeout from 'timeout-then'
import rump from 'rump'
import {colors} from 'gulp-util'
import {readFile, writeFile} from 'mz/fs'
import {resolve, sep} from 'path'
import {spy} from 'sinon'

const protocol = process.platform === 'win32' ? 'file:///' : 'file://',
      {stripColor} = colors

describe('tasks', function() {
  this.timeout(0)

  beforeEach(() => {
    rump.configure({
      environment: 'development',
      paths: {
        source: {root: 'test/fixtures', scripts: ''},
        destination: {root: 'tmp', scripts: ''},
      },
      scripts: {common: true},
    })
  })

  it('are added and defined', () => {
    const callback = spy()
    rump.on('gulp:main', callback)
    rump.on('gulp:scripts', callback)
    rump.addGulpTasks({prefix: 'spec'})
    callback.should.be.calledTwice()
    gulp.tasks['spec:info:scripts'].should.be.ok()
    gulp.tasks['spec:build:scripts'].should.be.ok()
  })

  it('displays correct information in info task', () => {
    const logs = [],
          {log} = console
    console.log = (...args) => logs.push(stripColor(args.join(' ')))
    gulp.start('spec:info')
    console.log = log
    logs.slice(-8).should.eql([
      '',
      '--- Scripts v0.7.0',
      `Processed scripts from test${sep}fixtures are copied with source maps to tmp`,
      `Common modules across processed scripts are built into tmp${sep}common.js`,
      'Affected files:',
      'coffee.coffee',
      'index.js',
      '',
    ])
  })

  describe('for building', () => {
    let originals

    before(async() => {
      originals = await Promise.all([
        readFile('test/fixtures/index.js'),
        readFile('test/fixtures/lib/index.js'),
      ])
      await new Promise(resolve => {
        gulp.task('postbuild', ['spec:watch'], resolve)
        gulp.start('postbuild')
      })
    })

    beforeEach(() => timeout(1000))

    afterEach(() => Promise.all([
      writeFile('test/fixtures/index.js', originals[0]),
      writeFile('test/fixtures/lib/index.js', originals[1]),
    ]))

    it('handles updates', async() => {
      const firstContent = await readFile('tmp/index.js')
      await timeout(1000)
      await writeFile('test/fixtures/lib/index.js', 'module.exports = "";')
      await timeout(1000)
      bufferEqual(firstContent, await readFile('tmp/index.js')).should.be.false()
    })

    it('handles source maps in development', async() => {
      const js = readFile('tmp/index.js'),
            coffee = readFile('tmp/coffee.js'),
            common = readFile('tmp/common.js'),
            contents = await Promise.all([common, js, coffee]),
            pathSet = contents
              .map(x => convert.fromSource(x.toString()))
              .map(x => x.getProperty('sources').sort()),
            paths = [].concat(...pathSet)
              .filter(x => x)
              .map(x => x.replace(protocol, '').split('/').join(sep))
      paths.should.have.length(11)
      paths.filter(x => !x.startsWith('webpack')).should.eql([
        resolve('node_modules/riot/riot.js'),
        resolve('test/fixtures/lib/html.html'),
        resolve('node_modules/lodash/internal/isObjectLike.js'),
        resolve('node_modules/lodash/lang/isNumber.js'),
        resolve('test/fixtures/index.js'),
        resolve('test/fixtures/lib/index.js'),
        resolve('test/fixtures/tags/type.tag'),
        resolve('test/fixtures/coffee.coffee'),
        resolve('test/fixtures/lib/json.json'),
        resolve('test/fixtures/tags/name.tag'),
      ])
    })
  })
})
<commit_msg>Test for production environment in info task<commit_after>import '../src'
import bufferEqual from 'buffer-equal'
import convert from 'convert-source-map'
import gulp from 'gulp'
import timeout from 'timeout-then'
import rump from 'rump'
import {colors} from 'gulp-util'
import {readFile, writeFile} from 'mz/fs'
import {resolve, sep} from 'path'
import {spy} from 'sinon'

const protocol = process.platform === 'win32' ? 'file:///' : 'file://',
      {stripColor} = colors

describe('tasks', function() {
  this.timeout(0)

  beforeEach(() => {
    rump.configure({
      environment: 'development',
      paths: {
        source: {root: 'test/fixtures', scripts: ''},
        destination: {root: 'tmp', scripts: ''},
      },
      scripts: {common: true},
    })
  })

  it('are added and defined', () => {
    const callback = spy()
    rump.on('gulp:main', callback)
    rump.on('gulp:scripts', callback)
    rump.addGulpTasks({prefix: 'spec'})
    callback.should.be.calledTwice()
    gulp.tasks['spec:info:scripts'].should.be.ok()
    gulp.tasks['spec:build:scripts'].should.be.ok()
  })

  it('displays correct information in info task', () => {
    const logs = [],
          {log} = console
    console.log = newLog
    gulp.start('spec:info')
    console.log = log
    logs.slice(-8).should.eql([
      '',
      '--- Scripts v0.7.0',
      `Processed scripts from test${sep}fixtures are copied with source maps to tmp`,
      `Common modules across processed scripts are built into tmp${sep}common.js`,
      'Affected files:',
      'coffee.coffee',
      'index.js',
      '',
    ])
    logs.length = 0
    console.log = newLog
    gulp.start('spec:info:prod')
    console.log = log
    logs.slice(-8).should.eql([
      '',
      '--- Scripts v0.7.0',
      `Processed scripts from test${sep}fixtures are minified and copied to tmp`,
      `Common modules across processed scripts are built into tmp${sep}common.js`,
      'Affected files:',
      'coffee.coffee',
      'index.js',
      '',
    ])
    rump.reconfigure({environment: 'development'})

    function newLog(...args) {
      logs.push(stripColor(args.join(' ')))
    }
  })

  describe('for building', () => {
    let originals

    before(async() => {
      originals = await Promise.all([
        readFile('test/fixtures/index.js'),
        readFile('test/fixtures/lib/index.js'),
      ])
      await new Promise(resolve => {
        gulp.task('postbuild', ['spec:watch'], resolve)
        gulp.start('postbuild')
      })
    })

    beforeEach(() => timeout(1000))

    afterEach(() => Promise.all([
      writeFile('test/fixtures/index.js', originals[0]),
      writeFile('test/fixtures/lib/index.js', originals[1]),
    ]))

    it('handles updates', async() => {
      const firstContent = await readFile('tmp/index.js')
      await timeout(1000)
      await writeFile('test/fixtures/lib/index.js', 'module.exports = "";')
      await timeout(1000)
      bufferEqual(firstContent, await readFile('tmp/index.js')).should.be.false()
    })

    it('handles source maps in development', async() => {
      const js = readFile('tmp/index.js'),
            coffee = readFile('tmp/coffee.js'),
            common = readFile('tmp/common.js'),
            contents = await Promise.all([common, js, coffee]),
            pathSet = contents
              .map(x => convert.fromSource(x.toString()))
              .map(x => x.getProperty('sources').sort()),
            paths = [].concat(...pathSet)
              .filter(x => x)
              .map(x => x.replace(protocol, '').split('/').join(sep))
      paths.should.have.length(11)
      paths.filter(x => !x.startsWith('webpack')).should.eql([
        resolve('node_modules/riot/riot.js'),
        resolve('test/fixtures/lib/html.html'),
        resolve('node_modules/lodash/internal/isObjectLike.js'),
        resolve('node_modules/lodash/lang/isNumber.js'),
        resolve('test/fixtures/index.js'),
        resolve('test/fixtures/lib/index.js'),
        resolve('test/fixtures/tags/type.tag'),
        resolve('test/fixtures/coffee.coffee'),
        resolve('test/fixtures/lib/json.json'),
        resolve('test/fixtures/tags/name.tag'),
      ])
    })
  })
})
<|endoftext|>


<commit_before>var Project = artifacts.require("Project");
var SimpleContractRegistry = artifacts.require("SimpleContractRegistry");
var AliceToken = artifacts.require("AliceToken");
var ImpactRegistry = artifacts.require("ImpactRegistry");

contract('Multiple donations', function(accounts) {
  var main = accounts[0];
  var validator = accounts[3];
  var beneficiary = accounts[4];
  var project, token, contractProvider;

  it("should link project to validator", function(done) {
    Project.deployed().then(function(instance) {
      project = instance;
      return project.setValidator(validator, {from: main})
    }).then(function() {
      return project.validatorAddress.call();
    }).then(function(address) {
      return assert.equal(address, validator, "Validator address wasn't set correctly");
    })
      .then(done)
      .catch(done);
  });

  it("should link project to beneficiary", function(done) {
    project.setBeneficiary(beneficiary, {from: main}).then(function() {
      return project.beneficiaryAddress.call();
    }).then(function(address) {
      return assert.equal(address, beneficiary, "Beneficiary address wasn't set correctly");
    })
      .then(done)
      .catch(done);
  });

  it("should link project to contract provider", function(done) {
    SimpleContractRegistry.deployed().then(function(instance) {
      contractProvider = instance;
      return project.setContractProvider(contractProvider.address, {from: main})
    }).then(function() {
      return project.CONTRACT_PROVIDER_ADDRESS.call();
    }).then(function(address) {
      return assert.equal(address, contractProvider.address, "Contract provider address wasn't set correctly");
    })
      .then(done)
      .catch(done);
  });

  it("should get token contract from registry", function(done) {
    SimpleContractRegistry.deployed().then(function(registryInstance) {
      return registryInstance.contracts.call('digitalGBP');
    }).then(function(address) {
      token = AliceToken.at(address);
      return assert.notEqual(token, undefined);
    })
      .then(done)
      .catch(done);
  });

  it("should donate from multiple accounts", function(done) {

    for (var i = 0; i < 50; i++) {
      var account = accounts[5+i];
      project.notify(account, 10, {from: main});
    }

    token.mint(project.address, 500, {from: main}).then(function () {
      return token.balanceOf.call(project.address, {from: main})
    }).then(function (balance) {
      return assert.equal(balance.valueOf(), 500, "500 wasn't minted");
    })
      .then(done)
      .catch(done);
  });

  it("should unlock outcome from multiple accounts", function (done) {
    token.balanceOf.call(project.address).then(function(balance) {
      return assert.equal(balance.valueOf(), 500, "500 wasn't in project before unlocking outcome");
    }).then(function() {
      return token.balanceOf.call(beneficiary);
    }).then(function(balance) {
      return assert.equal(balance.valueOf(), 0, "0 wasn't in beneficiary before unlocking outcome");
    }).then(function() {
      return project.validateOutcome("Outcome", 500, {from: validator});
    }).then(function() {
      return token.balanceOf.call(project.address);
    }).then(function(balance) {
      return assert.equal(balance.valueOf(), 0, "0 wasn't in project after unlocking outcome");
    }).then(function() {
      return token.balanceOf.call(beneficiary);
    }).then(function(balance) {
      return assert.equal(balance.valueOf(), 500, "500 wasn't in beneficiary after unlocking outcome");
    })
      .then(done)
      .catch(done);
  });

});<commit_msg>repair multiple-donations test<commit_after>var Project = artifacts.require("Project");
var SimpleContractRegistry = artifacts.require("SimpleContractRegistry");
var AliceToken = artifacts.require("AliceToken");
var ImpactRegistry = artifacts.require("ImpactRegistry");

contract('Multiple donations', function(accounts) {
  var main = accounts[0];
  var validator = accounts[3];
  var beneficiary = accounts[4];
  var project, token, contractProvider;

  it("should link project to validator", function(done) {
    Project.deployed().then(function(instance) {
      project = instance;
      return project.setValidator(validator, {from: main})
    }).then(function() {
      return project.validatorAddress.call();
    }).then(function(address) {
      return assert.equal(address, validator, "Validator address wasn't set correctly");
    })
      .then(done)
      .catch(done);
  });

  it("should link project to beneficiary", function(done) {
    project.setBeneficiary(beneficiary, {from: main}).then(function() {
      return project.beneficiaryAddress.call();
    }).then(function(address) {
      return assert.equal(address, beneficiary, "Beneficiary address wasn't set correctly");
    })
      .then(done)
      .catch(done);
  });

  it("should link project to contract provider", function(done) {
    SimpleContractRegistry.deployed().then(function(instance) {
      contractProvider = instance;
      return project.setContractProvider(contractProvider.address, {from: main})
    }).then(function() {
      return project.CONTRACT_PROVIDER_ADDRESS.call();
    }).then(function(address) {
      return assert.equal(address, contractProvider.address, "Contract provider address wasn't set correctly");
    })
      .then(done)
      .catch(done);
  });

  it("should get token contract from registry", function(done) {
    SimpleContractRegistry.deployed().then(function(registryInstance) {
      return registryInstance.contracts.call('digitalGBP');
    }).then(function(address) {
      token = AliceToken.at(address);
      return assert.notEqual(token, undefined);
    })
      .then(done)
      .catch(done);
  });

  it("should donate from multiple accounts", function(done) {

    for (var i = 0; i < 50; i++) {
      var account = accounts[5+i];
      project.notify(account, 10, {from: main});
    }

    token.mint(project.address, 500, {from: main}).then(function () {
      return token.balanceOf.call(project.address, {from: main})
    }).then(function (balance) {
      return assert.equal(balance.valueOf(), 500, "500 wasn't minted");
    })
      .then(done)
      .catch(done);
  });

  it("should set token for project", function (done) {
    project.setToken(token.address, {from: main}).then(function () {
      return project.getToken.call({from: main});
    }).then(function(tokenForProject) {
      return assert.equal(tokenForProject, token.address, "token was not set for project");
    })
      .then(done)
      .catch(done);
  });

  it("should validate outcome from multiple accounts", function (done) {
    token.balanceOf.call(project.address).then(function(balance) {
      return assert.equal(balance.valueOf(), 500, "500 wasn't in project before unlocking outcome");
    }).then(function() {
      return token.balanceOf.call(beneficiary);
    }).then(function(balance) {
      return assert.equal(balance.valueOf(), 0, "0 wasn't in beneficiary before unlocking outcome");
    }).then(function() {
      return project.validateOutcome("Outcome", 500, {from: validator});
    }).then(function() {
      return token.balanceOf.call(project.address);
    }).then(function(balance) {
      return assert.equal(balance.valueOf(), 0, "0 wasn't in project after unlocking outcome");
    }).then(function() {
      return token.balanceOf.call(beneficiary);
    }).then(function(balance) {
      return assert.equal(balance.valueOf(), 500, "500 wasn't in beneficiary after unlocking outcome");
    })
      .then(done)
      .catch(done);
  });

});<|endoftext|>


<commit_before>$(document).ready(function () {
    Spinner.selector = $('body > div.spinner');
    Window.selector = $('body > div.modal.window');
    Dialog.selector = $('body > div.dialog.template')
            .on('click', '.save,.yes,.no, .ok', function (e) {
                var form = $('body > div.dialog.template form.form-horizontal');
                var control = form.find('input[name=text]');

                if ($(this).hasClass('yes')) {
                    control.val(1);
                }

                if ($(this).hasClass('no')) {
                    control.val(0);
                }

                if ($(this).hasClass('ok')) {
                    return Dialog.callback();
                }

                form.find('.submit').trigger('click');
            })
            ;

    $('body > div.dialog.template form.form-horizontal')
            .submit(function (e) {
                e.preventDefault();

                var value = $(this).closest('.modal').find('.modal-body input[name="text"]').val().trim();

                value = value.length === 0 ? null : value;

                Dialog.callback(value);
                Dialog.callback = Dialog.defaultCallback;

                $(this).closest('.modal').modal('hide');
            });
});

var Window = {
    selector: null,
    show: function (title, content, width) {

        title = title || null;
        content = content || null;
        width = width || 'w75';

        var win = this.selector;

        if (title !== null) {
            win.find('.modal-header .title').html(title);
        }

        $(['w100', 'w75', 'w50']).each(function (i, e) {
            win.find('.modal-dialog').removeClass(e);
        });

        win.find('.modal-dialog').addClass(width);
        win.find('.modal-dialog .submit').addClass('hidden');

        win.find('.modal-body').html('');

        if(content !== null) {
            win.find('.modal-body').html($(content).html());
        }

        win.modal();
    },
    form: function (title, content, callback, width) {

        title = title || null;
        content = content || null;
        callback = callback || function (container) {
            console.log(container);
        };
        width = width || 'w75';

        var win = this.selector;

        if (title !== null) {
            win.find('.modal-header .title').html(title);
        }

        win
                .on('click', '.send', function (e) {
                    win.find('form').find('*[type=submit]').trigger('click');
                })
                .on('submit', 'form', function (e) {
                    e.preventDefault();

                    callback(win);

                    $(this).closest('.modal').modal('hide');
                });

        $(['w100', 'w75', 'w50']).each(function (i, e) {
            win.find('.modal-dialog').removeClass(e);
        });

        win.find('.modal-dialog').addClass(width);
        win.find('.modal-dialog .submit').removeClass('hidden');

        win.find('.modal-body').html('');

        if(content !== null) {
            win.find('.modal-body').html($(content).html());
            win.find('form').find('*[type=submit]').addClass('hidden');
        }

        win.modal();
    }
};

var Spinner = {
    selector: null,
    show: function () {
        this.selector.removeClass('hidden');
    },
    hide: function () {
        this.selector.addClass('hidden');
    }
};

/**
 * Obiekt zawierający typowe okienka dialogowe,
 * do komunikacji z użytkownikiem.
 * Na razie oparte na alertach i confirmach ale w planie
 * jest implementacja na behatowych modalach.
 *
 * Przykładowe użycie:
 *
 * Dialog.error('Wystąpił niespodziewany błąd.');
 * Dialog.warning('Nie znaleziono wyników wyszukiwania.');
 * Dialog.info('Dodanie użytkownika zakończyło się powodzeniem.');
 * Dialog.input('Podaj swoje imię:')
 * Dialog.question('Czy jesteś pełnoletni?');
 * Dialog.confirmation('Czy na pewno chcesz usunąć tę pozycję?');
 *
 * Zmiana domyślnych tytułów okien:
 *
 * Dialog.setTypes({
 *    error: 'Błąd',
 *    warning: 'Ostrzeżenie',
 *    info: 'Informacja',
 *    input: 'Wprowadź dane',
 *    question: 'Pytanie',
 *    confirmation: 'Potwierdzenie'
 * });
 *
 * @type {{setTypes: setTypes, types: {error: string, warning: string, info: string, question: string, confirmation: string}, dialog: dialog, error: error, warning: warning, question: question, info: info, confirmation: confirmation}}
 */
var Dialog = {
    selector: null,
    value: null,
    val: function () {
        return this.value;
    },
    defaultCallback: function (val) {

    },
    callback: function (val) {

    },
    /**
     * Metoda do zmiany domyślnych tytułów okien dialogowych, np. w innych językach.
     *
     * @param types obiekt
     */
    setTypes: function (types) {
        for (var key in types) {
            if (this.types.hasOwnProperty(key)) {
                this.types[key] = types[key];
            }
        }
    },
    types: {
        error: 'Error',
        warning: 'Warning',
        info: 'Information',
        input: 'Input',
        question: 'Question',
        confirmation: 'Confirmation'
    },
    icons: {
        error: 'fa-times-circle',
        warning: 'fa-exclamation-circle',
        info: 'fa-info-circle',
        input: 'fa-edit',
        question: 'fa-question-circle',
        confirmation: 'fa-check-square-o'
    },
    buttons: {
        error: ['ok'],
        warning: ['ok'],
        info: ['ok'],
        input: ['save', 'cancel'],
        question: ['yes', 'no'],
        confirmation: ['yes', 'no']
    },
    dialog: function (type, message, title, callback) {
        title = title || this.types[type];
        callback = callback || this.callback;
        message = message || title;
        var icon = this.icons[type];

        var dialog = this.selector;

        if (dialog) {
            dialog.find('.modal-title .title').html(title);
            dialog.find('.modal-body .message').html(message);

            for (var t in this.types) {
                if (this.types.hasOwnProperty(t)) {
                    dialog.find('i').removeClass(this.icons[t]).removeClass(t);
                }
            }

            dialog.find('form').get(0).reset();
            dialog.find('.modal-header i').addClass(icon);
            dialog.find('.modal-body i').addClass(icon).addClass(type);

            dialog.find('.modal-footer .btn').each(function (i, e) {
                $(e).addClass('hidden');
            });

            dialog.find('input[name=text]').addClass('hidden');

            if (type == 'input') {
                Dialog.value = null;
                dialog.find('input[name=text]').removeClass('hidden');
            }

            $(this.buttons[type]).each(function (i, e) {
                var btn = dialog.find('.modal-footer .btn.' + e);
                btn.removeClass('hidden');
            });

            Dialog.callback = callback;
        }

        return dialog.modal();
    },
    /**
     * Okienko dialogowe błędu
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    error: function (message, title, callback) {
        callback = callback || null;
        return this.dialog('error', message, title, callback);
    },
    /**
     * Okienko dialogowe informacyjne
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    info: function (message, title, callback) {
        message = message || 'OK';
        callback = callback || null;
        return this.dialog('info', message, title, callback);
    },
    /**
     * Okienko dialogowe ostrzeżenia
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    warning: function (message, title, callback) {
        callback = callback || null;
        return this.dialog('warning', message, title, callback);
    },
    /**
     * Okienko dialogowe wprowadzenia danych
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    input: function (message, title, callback) {
        callback = callback || function (value) {
            console.log([title, message, value]);
        };
        return this.dialog('input', message, title, callback);
    },
    /**
     * Okienko dialogowe zapytania
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po wybraniu opcji zatwierdzającej (guzik TAK)
     */
    question: function (message, title, callback) {
        message = message || 'Are you sure?';
        title = title || this.types.question;
        callback = callback || function (val) {
            console.log([title, message, val]);
            return true;
        };
        return this.dialog('question', message, title, callback);
    },
    /**
     * Okienko dialogowe potwierdzenie
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po wybraniu opcji zatwierdzającej (guzik TAK)
     */
    confirmation: function (message, title, callback) {
        message = message || 'Are you sure';
        title = title || this.types.confirmation;
        callback = callback || function (val) {
            console.log([title, message, val]);
            return true;
        };
        return this.dialog('confirmation', message, title, callback);
    }
};
<commit_msg>Fix modal windows<commit_after>$(document).ready(function () {
    Spinner.selector = $('body > div.spinner');
    Window.selector = $('body > div.modal.window');
    Dialog.selector = $('body > div.dialog.template')
            .on('click', '.save,.yes,.no, .ok', function (e) {
                var form = $('body > div.dialog.template form.form-horizontal');
                var control = form.find('input[name=text]');

                if ($(this).hasClass('yes')) {
                    control.val(1);
                }

                if ($(this).hasClass('no')) {
                    control.val(0);
                }

                if ($(this).hasClass('ok')) {
                    return Dialog.callback();
                }

                form.find('.submit').trigger('click');
            })
            ;

    $('body > div.dialog.template form.form-horizontal')
            .submit(function (e) {
                e.preventDefault();

                var value = $(this).closest('.modal').find('.modal-body input[name="text"]').val().trim();

                value = value.length === 0 ? null : value;

                Dialog.callback(value);
                Dialog.callback = Dialog.defaultCallback;

                $(this).closest('.modal').modal('hide');
            });
});

var Window = {
    selector: null,
    show: function (title, content, width) {

        title = title || null;
        content = content || null;
        width = width || 'w75';

        var win = this.selector;

        if (title !== null) {
            win.find('.modal-header .title').html(title);
        }

        $(['w100', 'w75', 'w50']).each(function (i, e) {
            win.find('.modal-dialog').removeClass(e);
        });

        win.find('.modal-dialog').addClass(width);
        win.find('.modal-dialog .submit').addClass('hidden');

        win.find('.modal-body').html('');

        if(content !== null) {
            win.find('.modal-body').html($(content).html());
        }

        win.modal();
    },
    form: function (title, content, callback, width) {

        title = title || null;
        content = content || null;
        callback = callback || function (container) {
            console.log(container);
        };
        width = width || 'w75';

        var win = this.selector;

        if (title !== null) {
            win.find('.modal-header .title').html(title);
        }

        win
                .on('click', '.submit', function (e) {
                    win.find('form').find('*[type=submit]').trigger('click');
                })
                .on('submit', 'form', function (e) {
                    e.preventDefault();

                    callback(win);

                    $(this).closest('.modal').modal('hide');
                });

        $(['w100', 'w75', 'w50']).each(function (i, e) {
            win.find('.modal-dialog').removeClass(e);
        });

        win.find('.modal-dialog').addClass(width);
        win.find('.modal-dialog .submit').removeClass('hidden');

        win.find('.modal-body').html('');

        if(content !== null) {
            win.find('.modal-body').html($(content).html());
            win.find('form').find('*[type=submit]').addClass('hidden');
        }

        win.modal();
    }
};

var Spinner = {
    selector: null,
    show: function () {
        this.selector.removeClass('hidden');
    },
    hide: function () {
        this.selector.addClass('hidden');
    }
};

/**
 * Obiekt zawierający typowe okienka dialogowe,
 * do komunikacji z użytkownikiem.
 * Na razie oparte na alertach i confirmach ale w planie
 * jest implementacja na behatowych modalach.
 *
 * Przykładowe użycie:
 *
 * Dialog.error('Wystąpił niespodziewany błąd.');
 * Dialog.warning('Nie znaleziono wyników wyszukiwania.');
 * Dialog.info('Dodanie użytkownika zakończyło się powodzeniem.');
 * Dialog.input('Podaj swoje imię:')
 * Dialog.question('Czy jesteś pełnoletni?');
 * Dialog.confirmation('Czy na pewno chcesz usunąć tę pozycję?');
 *
 * Zmiana domyślnych tytułów okien:
 *
 * Dialog.setTypes({
 *    error: 'Błąd',
 *    warning: 'Ostrzeżenie',
 *    info: 'Informacja',
 *    input: 'Wprowadź dane',
 *    question: 'Pytanie',
 *    confirmation: 'Potwierdzenie'
 * });
 *
 * @type {{setTypes: setTypes, types: {error: string, warning: string, info: string, question: string, confirmation: string}, dialog: dialog, error: error, warning: warning, question: question, info: info, confirmation: confirmation}}
 */
var Dialog = {
    selector: null,
    value: null,
    val: function () {
        return this.value;
    },
    defaultCallback: function (val) {

    },
    callback: function (val) {

    },
    /**
     * Metoda do zmiany domyślnych tytułów okien dialogowych, np. w innych językach.
     *
     * @param types obiekt
     */
    setTypes: function (types) {
        for (var key in types) {
            if (this.types.hasOwnProperty(key)) {
                this.types[key] = types[key];
            }
        }
    },
    types: {
        error: 'Error',
        warning: 'Warning',
        info: 'Information',
        input: 'Input',
        question: 'Question',
        confirmation: 'Confirmation'
    },
    icons: {
        error: 'fa-times-circle',
        warning: 'fa-exclamation-circle',
        info: 'fa-info-circle',
        input: 'fa-edit',
        question: 'fa-question-circle',
        confirmation: 'fa-check-square-o'
    },
    buttons: {
        error: ['ok'],
        warning: ['ok'],
        info: ['ok'],
        input: ['save', 'cancel'],
        question: ['yes', 'no'],
        confirmation: ['yes', 'no']
    },
    dialog: function (type, message, title, callback) {
        title = title || this.types[type];
        callback = callback || this.callback;
        message = message || title;
        var icon = this.icons[type];

        var dialog = this.selector;

        if (dialog) {
            dialog.find('.modal-title .title').html(title);
            dialog.find('.modal-body .message').html(message);

            for (var t in this.types) {
                if (this.types.hasOwnProperty(t)) {
                    dialog.find('i').removeClass(this.icons[t]).removeClass(t);
                }
            }

            dialog.find('form').get(0).reset();
            dialog.find('.modal-header i').addClass(icon);
            dialog.find('.modal-body i').addClass(icon).addClass(type);

            dialog.find('.modal-footer .btn').each(function (i, e) {
                $(e).addClass('hidden');
            });

            dialog.find('input[name=text]').addClass('hidden');

            if (type == 'input') {
                Dialog.value = null;
                dialog.find('input[name=text]').removeClass('hidden');
            }

            $(this.buttons[type]).each(function (i, e) {
                var btn = dialog.find('.modal-footer .btn.' + e);
                btn.removeClass('hidden');
            });

            Dialog.callback = callback;
        }

        return dialog.modal();
    },
    /**
     * Okienko dialogowe błędu
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    error: function (message, title, callback) {
        callback = callback || null;
        return this.dialog('error', message, title, callback);
    },
    /**
     * Okienko dialogowe informacyjne
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    info: function (message, title, callback) {
        message = message || 'OK';
        callback = callback || null;
        return this.dialog('info', message, title, callback);
    },
    /**
     * Okienko dialogowe ostrzeżenia
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    warning: function (message, title, callback) {
        callback = callback || null;
        return this.dialog('warning', message, title, callback);
    },
    /**
     * Okienko dialogowe wprowadzenia danych
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po zamknięciu okienka
     */
    input: function (message, title, callback) {
        callback = callback || function (value) {
            console.log([title, message, value]);
        };
        return this.dialog('input', message, title, callback);
    },
    /**
     * Okienko dialogowe zapytania
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po wybraniu opcji zatwierdzającej (guzik TAK)
     */
    question: function (message, title, callback) {
        message = message || 'Are you sure?';
        title = title || this.types.question;
        callback = callback || function (val) {
            console.log([title, message, val]);
            return true;
        };
        return this.dialog('question', message, title, callback);
    },
    /**
     * Okienko dialogowe potwierdzenie
     *
     * @param message
     * @param title tytuł (parametr opcjonalny)
     * @param callback funkcja wykonywana po wybraniu opcji zatwierdzającej (guzik TAK)
     */
    confirmation: function (message, title, callback) {
        message = message || 'Are you sure';
        title = title || this.types.confirmation;
        callback = callback || function (val) {
            console.log([title, message, val]);
            return true;
        };
        return this.dialog('confirmation', message, title, callback);
    }
};
<|endoftext|>


<commit_before>async function addCollection() {
    await syncTree();

    var selectedCollection = getSelectedCollection();
    var selectedNode = getSelectedNode();

    var newCollection = {
        text: $('#addCollectionName').val(),
        showBookmarkDescription: true,
        showBookmarkIcon: true,
        showBookmarksAsCards: false,
        bookmarkIconSize: 16,
        nodeId: getRandomInt(Number.MAX_SAFE_INTEGER)
    };

    if (typeof selectedCollection == 'undefined' || selectedCollection.id === "tree") {
        selectedCollection = newCollection;
        updateTree(selectedCollection);
    } else {
        if (typeof selectedCollection.nodes == 'undefined') {
            selectedCollection.nodes = [];
        }

        newCollection.parentId = selectedCollection.nodeId;

        tree.addNode(newCollection, selectedNode);

        updateTree();
    }

    $('#addCollectionName').val("");
}

async function addBookmark() {
    $("#addBookmarkSpinner").show();

    await syncTree();

    var selectedCollection = getSelectedCollection();

    if (typeof selectedCollection.bookmarks == 'undefined') {
        selectedCollection.bookmarks = [];
    }

    var bookmark = {
        text: $('#addBookmarkName').val(),
        url: $('#addBookmarkUrl').val(),
        description: $('#addBookmarkDescription').val()
    };

    // Get Favicon
    var faviconUrl = "https://favicon.sboulema.nl/favicon?url=" + $('#addBookmarkUrl').val();
    var faviconData = await $.get("https://favicon.sboulema.nl/favicon?base64=true&url=" + faviconUrl);

    if (typeof faviconData === 'undefined') {
        faviconUrl = "";
        faviconData = "";
    }

    bookmark.icon = faviconUrl;
    bookmark.iconData = faviconData;

    selectedCollection.bookmarks.push(bookmark);

    $('#addBookmarkName').val("");
    $('#addBookmarkUrl').val("");
    $('#addBookmarkDescription').val("");

    selectedCollection.tags = [selectedCollection.bookmarks.length];

    updateTree();

    $("#addBookmarkModal").modal('hide');
}

function deleteBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks.splice(bookmarkIndex, 1);

    updateTree();
}

function deleteCollection() {
    var collection = getSelectedCollection();

    if (typeof collection.parentId != 'undefined') {
        var parent = tree.getDataById(collection.parentId);

        $.each(parent.nodes, function (index, node) {
            if (node.nodeId === collection.nodeId) {
                parent.nodes.splice(index, 1);
                updateTree();
                return false;
            }
        });
    } else {
        var index = getIndex(collection);
        var parent = getParent(collection)[0];

        if (parent.id === "tree") {
            var collections = getCollections();

            collections.splice(index, 1);

            updateTree(collections, true);
        } else {
            parent.nodes.slice(index, 1);

            updateTree();
        }
    }
}

function editBookmark(node, isCard) {
    var bookmark;
    var bookmarkIndex;

    if (isCard) {
        bookmark = getSelectedBookmarkCard(node);
        bookmarkIndex = getIndexCard(node);
    } else {
        bookmark = getSelectedBookmark(node);
        bookmarkIndex = getIndex(node);
    }
    var selectedCollection = getSelectedCollection();

    $("#editName").val(bookmark.text);
    $("#editUrl").val(bookmark.url);
    $("#editIcon").val(bookmark.icon);
    $("#editIconAddon").attr("src", bookmark.icon);
    $("#editIndex").text(bookmarkIndex);
    $("#editDescription").val(bookmark.description);
    $("#editTags").val(bookmark.tags);
    $("#editBookmarkCollection").val(selectedCollection.text);

    $('#editModal').modal('show');
}

// Show 'Edit Collection' modal
function editCollection() {
    var selectedCollection = getSelectedCollection();

    $("#editCollectionName").val(selectedCollection.text);
    $("#editCollectionIcon").val(selectedCollection.icon);
    $("#editCollectionBackground").val(selectedCollection.background);
    $("#showBookmarkIconCheckbox").prop('checked', selectedCollection.showBookmarkIcon);
    $("#showBookmarkDescriptionCheckbox").prop('checked', selectedCollection.showBookmarkDescription);
    $("#bookmarkIconSizeSlider").val(selectedCollection.bookmarkIconSize);
    $("#showBookmarksAsCardsCheckbox").prop('checked', selectedCollection.showBookmarksAsCards);
    $("#editCollectionIconAddon").html("");

    $("#editIndex").text(getIndex(selectedCollection));

    var parentCollection = getParent(selectedCollection);
    if (typeof parentCollection !== 'undefined') {
        $("#editCollectionParent").val(getParent(selectedCollection).text);
    }

    $('#editCollectionModal').modal('show');
}

function saveBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks[bookmarkIndex].text = $("#editName").val();
    selectedCollection.bookmarks[bookmarkIndex].url = $("#editUrl").val();
    selectedCollection.bookmarks[bookmarkIndex].icon = $("#editIcon").val();
    selectedCollection.bookmarks[bookmarkIndex].description = $("#editDescription").val();
    selectedCollection.bookmarks[bookmarkIndex].tags = $("#editTags").val();

    if ($("#editIcon").val().startsWith("https://favicon.sboulema.nl/favicon")) {
        $.get("https://favicon.sboulema.nl/favicon?base64=true&url=" + $("#editUrl").val(), function(data) {
            selectedCollection.bookmarks[bookmarkIndex].iconData = data;
            updateTree();
        });
    }

    var moveToCollectionId = $("#editMoveToCollection").text();

    if (moveToCollectionId != "") {
        var moveToCollection = tree.getDataById($("#editMoveToCollection").text());

        if (typeof moveToCollection != 'undefined' &&
            typeof moveToCollection.nodeId != 'undefined' &&
            selectedCollection !== moveToCollection) {
            var bookmark = selectedCollection.bookmarks[bookmarkIndex];
            selectedCollection.bookmarks.splice(bookmarkIndex, 1);
            moveToCollection.bookmarks.push(bookmark);
            moveToCollection.tags = [moveToCollection.bookmarks.length];
            selectedCollection.tags = [selectedCollection.bookmarks.length];
        }
    }

    updateTree();
}

function saveCollection() {
    var selectedCollection = getSelectedCollection();

    selectedCollection.text = $("#editCollectionName").val();
    selectedCollection.icon = $("#editCollectionIcon").val();
    selectedCollection.background = $("#editCollectionBackground").val();
    selectedCollection.showBookmarkIcon = $("#showBookmarkIconCheckbox").is(':checked');
    selectedCollection.showBookmarkDescription = $("#showBookmarkDescriptionCheckbox").is(':checked');
    selectedCollection.bookmarkIconSize = $("#bookmarkIconSizeSlider").val();
    selectedCollection.showBookmarksAsCards = $("#showBookmarksAsCardsCheckbox").is(':checked');

    // Check if we need to move the collection to a new parent
    var moveToCollection = tree.getDataByText($("#editMoveToCollection").text());
    var parentCollection = getParent(selectedCollection);

    if (typeof moveToCollection != 'undefined' &&
        typeof moveToCollection.nodeId != 'undefined' &&
        moveToCollection !== parentCollection) {
        var copyCollection = $.extend(true, {}, selectedCollection);

        if (typeof moveToCollection.nodes == 'undefined') {
            moveToCollection.nodes = [];
        }

        moveToCollection.nodes.push(copyCollection);

        deleteCollection();
    }

    tree.reload();
    tree.expand(tree.getNodeById(selectedCollection.parentId));
    tree.select(tree.getNodeById(selectedCollection.nodeId));

    updateTree();
}

function showBookmarks(collection, showAsCards) {
    var bookmarksHtml = "";

    if (collection.showBookmarksAsCards) {
        $.each(collection.bookmarks, function (index, bookmark) {
            var item = "
";

            item += "";
            if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                item += "";
            } else {
                item += "";
            }
            item += "";

            item += "<button type='button' class='btn btn-primary btn-sm cardEditButton' onclick='editBookmark(this, true)'>" +
                "</button>";

            item += "
";

            bookmarksHtml += item;
        });

    } else {
        $.each(collection.bookmarks, function (index, bookmark) {

            var item = "
";
            item += "
";

            item += "
";

            if (collection.showBookmarkIcon) {
                if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                    item += "";
                    item += "" + bookmark.text + "";
                } else {
                    item += "";
                    item += "" + bookmark.text + "";
                }
            }

            if (collection.showBookmarkDescription && typeof bookmark.description != 'undefined' && bookmark.description != "") {
                item += " - " + bookmark.description + "";
            }

            item += showTags(bookmark);

            item += "
";

            item += createBookmarkButtons(bookmark);

            item += "
";
            item += "
"

            bookmarksHtml += item;
        });
    }

    return bookmarksHtml;
}

function showTags(bookmark) {
    var tags = $("", {
        class: "tag"
    });

    if (typeof bookmark.tags === 'undefined' || bookmark.tags === "") {
        return tags.html();
    }

    bookmark.tags.split(",").forEach(tag => {
        tags.append("" + tag + "");
    });

    return tags[0].outerHTML;
}

function createBookmarkButtons(bookmark) {
    var buttons = $("
", {
        class: "col-auto btn-toolbar",
        role: "toolbar"
    });

    buttons.append(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='editBookmark(this)'>" +
        "</button>" +
        "
"
    );

    var copyButton = $(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm btn-clipboard' data-clipboard-text='" +
        bookmark.url +  "'>" + "</button>" +
        "
"
    ).appendTo(buttons);

    if (navigator.share) {
        copyButton.addClass("mr-2");

        buttons.append(
            "
" +
            "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='shareBookmark(this)'>" +
            "</button>" +
            "
"
        );
    }

    return buttons[0].outerHTML;
}

function shareBookmark(node) {
    var bookmark = getSelectedBookmark(node);

    navigator.share({
        title: bookmark.text,
        text: bookmark.description,
        url: bookmark.url,
    })
      .then(() => console.log('Successful share'))
      .catch((error) => console.log('Error sharing', error));
}

function getCollectionBackground(collection) {
    if (typeof collection !== 'undefined' &&
        typeof collection.background !== 'undefined' &&
        collection.background !== "inherit") {
        return collection.background;
    }

    if (collection.parentId === -1) {
        return "";
    }

    return getCollectionBackground(getParent(collection));
}

function getParent(collection) {
    return tree.getDataById(collection.parentId);
}

function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}<commit_msg>:bug: Fix favicon handling<commit_after>async function addCollection() {
    await syncTree();

    var selectedCollection = getSelectedCollection();
    var selectedNode = getSelectedNode();

    var newCollection = {
        text: $('#addCollectionName').val(),
        showBookmarkDescription: true,
        showBookmarkIcon: true,
        showBookmarksAsCards: false,
        bookmarkIconSize: 16,
        nodeId: getRandomInt(Number.MAX_SAFE_INTEGER)
    };

    if (typeof selectedCollection == 'undefined' || selectedCollection.id === "tree") {
        selectedCollection = newCollection;
        updateTree(selectedCollection);
    } else {
        if (typeof selectedCollection.nodes == 'undefined') {
            selectedCollection.nodes = [];
        }

        newCollection.parentId = selectedCollection.nodeId;

        tree.addNode(newCollection, selectedNode);

        updateTree();
    }

    $('#addCollectionName').val("");
}

async function addBookmark() {
    $("#addBookmarkSpinner").show();

    await syncTree();

    var selectedCollection = getSelectedCollection();

    if (typeof selectedCollection.bookmarks == 'undefined') {
        selectedCollection.bookmarks = [];
    }

    var bookmark = {
        text: $('#addBookmarkName').val(),
        url: $('#addBookmarkUrl').val(),
        description: $('#addBookmarkDescription').val()
    };

    // Get Favicon
    var faviconUrl = "https://favicon.sboulema.nl/favicon?url=" + $('#addBookmarkUrl').val();
    var faviconData = await $.get("https://favicon.sboulema.nl/favicon?base64=true&url=" + $('#addBookmarkUrl').val());

    if (typeof faviconData === 'undefined') {
        faviconUrl = "";
        faviconData = "";
    }

    bookmark.icon = faviconUrl;
    bookmark.iconData = faviconData;

    selectedCollection.bookmarks.push(bookmark);

    $('#addBookmarkName').val("");
    $('#addBookmarkUrl').val("");
    $('#addBookmarkDescription').val("");

    selectedCollection.tags = [selectedCollection.bookmarks.length];

    updateTree();

    $("#addBookmarkModal").modal('hide');
}

function deleteBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks.splice(bookmarkIndex, 1);

    updateTree();
}

function deleteCollection() {
    var collection = getSelectedCollection();

    if (typeof collection.parentId != 'undefined') {
        var parent = tree.getDataById(collection.parentId);

        $.each(parent.nodes, function (index, node) {
            if (node.nodeId === collection.nodeId) {
                parent.nodes.splice(index, 1);
                updateTree();
                return false;
            }
        });
    } else {
        var index = getIndex(collection);
        var parent = getParent(collection)[0];

        if (parent.id === "tree") {
            var collections = getCollections();

            collections.splice(index, 1);

            updateTree(collections, true);
        } else {
            parent.nodes.slice(index, 1);

            updateTree();
        }
    }
}

function editBookmark(node, isCard) {
    var bookmark;
    var bookmarkIndex;

    if (isCard) {
        bookmark = getSelectedBookmarkCard(node);
        bookmarkIndex = getIndexCard(node);
    } else {
        bookmark = getSelectedBookmark(node);
        bookmarkIndex = getIndex(node);
    }
    var selectedCollection = getSelectedCollection();

    $("#editName").val(bookmark.text);
    $("#editUrl").val(bookmark.url);
    $("#editIcon").val(bookmark.icon);
    $("#editIconAddon").attr("src", bookmark.icon);
    $("#editIndex").text(bookmarkIndex);
    $("#editDescription").val(bookmark.description);
    $("#editTags").val(bookmark.tags);
    $("#editBookmarkCollection").val(selectedCollection.text);

    $('#editModal').modal('show');
}

// Show 'Edit Collection' modal
function editCollection() {
    var selectedCollection = getSelectedCollection();

    $("#editCollectionName").val(selectedCollection.text);
    $("#editCollectionIcon").val(selectedCollection.icon);
    $("#editCollectionBackground").val(selectedCollection.background);
    $("#showBookmarkIconCheckbox").prop('checked', selectedCollection.showBookmarkIcon);
    $("#showBookmarkDescriptionCheckbox").prop('checked', selectedCollection.showBookmarkDescription);
    $("#bookmarkIconSizeSlider").val(selectedCollection.bookmarkIconSize);
    $("#showBookmarksAsCardsCheckbox").prop('checked', selectedCollection.showBookmarksAsCards);
    $("#editCollectionIconAddon").html("");

    $("#editIndex").text(getIndex(selectedCollection));

    var parentCollection = getParent(selectedCollection);
    if (typeof parentCollection !== 'undefined') {
        $("#editCollectionParent").val(getParent(selectedCollection).text);
    }

    $('#editCollectionModal').modal('show');
}

function saveBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks[bookmarkIndex].text = $("#editName").val();
    selectedCollection.bookmarks[bookmarkIndex].url = $("#editUrl").val();
    selectedCollection.bookmarks[bookmarkIndex].icon = $("#editIcon").val();
    selectedCollection.bookmarks[bookmarkIndex].description = $("#editDescription").val();
    selectedCollection.bookmarks[bookmarkIndex].tags = $("#editTags").val();

    if ($("#editIcon").val().startsWith("https://favicon.sboulema.nl/favicon")) {
        $.get("https://favicon.sboulema.nl/favicon?base64=true&url=" + $("#editUrl").val(), function(data) {
            selectedCollection.bookmarks[bookmarkIndex].iconData = data;
            updateTree();
        });
    } else {
        selectedCollection.bookmarks[bookmarkIndex].iconData = "";
    }

    var moveToCollectionId = $("#editMoveToCollection").text();

    if (moveToCollectionId != "") {
        var moveToCollection = tree.getDataById($("#editMoveToCollection").text());

        if (typeof moveToCollection != 'undefined' &&
            typeof moveToCollection.nodeId != 'undefined' &&
            selectedCollection !== moveToCollection) {
            var bookmark = selectedCollection.bookmarks[bookmarkIndex];
            selectedCollection.bookmarks.splice(bookmarkIndex, 1);
            moveToCollection.bookmarks.push(bookmark);
            moveToCollection.tags = [moveToCollection.bookmarks.length];
            selectedCollection.tags = [selectedCollection.bookmarks.length];
        }
    }

    updateTree();
}

function saveCollection() {
    var selectedCollection = getSelectedCollection();

    selectedCollection.text = $("#editCollectionName").val();
    selectedCollection.icon = $("#editCollectionIcon").val();
    selectedCollection.background = $("#editCollectionBackground").val();
    selectedCollection.showBookmarkIcon = $("#showBookmarkIconCheckbox").is(':checked');
    selectedCollection.showBookmarkDescription = $("#showBookmarkDescriptionCheckbox").is(':checked');
    selectedCollection.bookmarkIconSize = $("#bookmarkIconSizeSlider").val();
    selectedCollection.showBookmarksAsCards = $("#showBookmarksAsCardsCheckbox").is(':checked');

    // Check if we need to move the collection to a new parent
    var moveToCollection = tree.getDataByText($("#editMoveToCollection").text());
    var parentCollection = getParent(selectedCollection);

    if (typeof moveToCollection != 'undefined' &&
        typeof moveToCollection.nodeId != 'undefined' &&
        moveToCollection !== parentCollection) {
        var copyCollection = $.extend(true, {}, selectedCollection);

        if (typeof moveToCollection.nodes == 'undefined') {
            moveToCollection.nodes = [];
        }

        moveToCollection.nodes.push(copyCollection);

        deleteCollection();
    }

    tree.reload();
    tree.expand(tree.getNodeById(selectedCollection.parentId));
    tree.select(tree.getNodeById(selectedCollection.nodeId));

    updateTree();
}

function showBookmarks(collection, showAsCards) {
    var bookmarksHtml = "";

    if (collection.showBookmarksAsCards) {
        $.each(collection.bookmarks, function (index, bookmark) {
            var item = "
";

            item += "";
            if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                item += "";
            } else {
                item += "";
            }
            item += "";

            item += "<button type='button' class='btn btn-primary btn-sm cardEditButton' onclick='editBookmark(this, true)'>" +
                "</button>";

            item += "
";

            bookmarksHtml += item;
        });

    } else {
        $.each(collection.bookmarks, function (index, bookmark) {

            var item = "
";
            item += "
";

            item += "
";

            if (collection.showBookmarkIcon) {
                if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                    item += "";
                    item += "" + bookmark.text + "";
                } else {
                    item += "";
                    item += "" + bookmark.text + "";
                }
            }

            if (collection.showBookmarkDescription && typeof bookmark.description != 'undefined' && bookmark.description != "") {
                item += " - " + bookmark.description + "";
            }

            item += showTags(bookmark);

            item += "
";

            item += createBookmarkButtons(bookmark);

            item += "
";
            item += "
"

            bookmarksHtml += item;
        });
    }

    return bookmarksHtml;
}

function showTags(bookmark) {
    var tags = $("", {
        class: "tag"
    });

    if (typeof bookmark.tags === 'undefined' || bookmark.tags === "") {
        return tags.html();
    }

    bookmark.tags.split(",").forEach(tag => {
        tags.append("" + tag + "");
    });

    return tags[0].outerHTML;
}

function createBookmarkButtons(bookmark) {
    var buttons = $("
", {
        class: "col-auto btn-toolbar",
        role: "toolbar"
    });

    buttons.append(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='editBookmark(this)'>" +
        "</button>" +
        "
"
    );

    var copyButton = $(
        "
" +
        "<button type='button' class='btn btn-outline-secondary btn-sm btn-clipboard' data-clipboard-text='" +
        bookmark.url +  "'>" + "</button>" +
        "
"
    ).appendTo(buttons);

    if (navigator.share) {
        copyButton.addClass("mr-2");

        buttons.append(
            "
" +
            "<button type='button' class='btn btn-outline-secondary btn-sm' onclick='shareBookmark(this)'>" +
            "</button>" +
            "
"
        );
    }

    return buttons[0].outerHTML;
}

function shareBookmark(node) {
    var bookmark = getSelectedBookmark(node);

    navigator.share({
        title: bookmark.text,
        text: bookmark.description,
        url: bookmark.url,
    })
      .then(() => console.log('Successful share'))
      .catch((error) => console.log('Error sharing', error));
}

function getCollectionBackground(collection) {
    if (typeof collection !== 'undefined' &&
        typeof collection.background !== 'undefined' &&
        collection.background !== "inherit") {
        return collection.background;
    }

    if (collection.parentId === -1) {
        return "";
    }

    return getCollectionBackground(getParent(collection));
}

function getParent(collection) {
    return tree.getDataById(collection.parentId);
}

function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}<|endoftext|>