<commit_before>
import Lab from 'lab';
var lab = exports.lab = Lab.script();

import Code from 'code';
var describe = lab.describe;
var it = lab.it;
var beforeEach = lab.beforeEach;
var expect = Code.expect;

import eureka from '../../lib';
import config from '../config';
import fixtures from '../utils/fixtures';

const jsonApiMime = 'application/vnd.api+json';

describe('Route [find]', function() {

    /** load the server **/
    var server;
    lab.before(function(done) {
        eureka(config).compose(function(err, s) {
            expect(err).to.not.exists();
            server = s;
            done();
        });
    });

     /** load the fixtures **/
    beforeEach(function(done){
        fixtures.clear(server).then(() => {
            return fixtures.genericDocuments(server);
        }).then(() => {
            done();
        }).catch((error) => {
            console.log(error);
        });
    });

    it('should return all documents', function(done){
        let options = {
            method: 'GET',
            url: '/api/1/generics'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(10);
            expect(data[0].type).to.equal('Generic');

            done();
        });
    });

    it('should filter by id', function(done){
        let options = {
            method: 'GET',
            url: '/api/1/generics?filter[id]=generic3'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(1);
            expect(data[0].id).to.equal('generic3');
            expect(data[0].type).to.equal('Generic');
            expect(data[0].attributes).to.exist();
            done();
        });
    });

    it('should filter by ids', function(done){
        let options = {
            method: 'GET',
            url: '/api/1/generics?filter[id][$in]=["generic3","generic2"]'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(2);
            expect(data[0].id).to.equal('generic3');
            expect(data[0].attributes).to.exist();
            expect(data[1].id).to.equal('generic2');
            expect(data[1].attributes).to.exist();
            done();
        });
    });

    it('should sort the documents', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?sort=-integer`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(10);
            expect(data.map((o) => {
                return o.attributes.integer;
            })).to.deep.equal([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
            done();
        });
    });

    it('should limit the documents', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?limit=5`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(5);
            done();
        });
    });

    it('should return only the specified fields', function(done){
        let options = {
            method: 'GET',
            url: `/api/1/generics?fields=["integer","text"]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let data = response.result.data;
            expect(data).to.be.an.array();
            data.forEach(function(item) {
                expect(item.attributes).to.only.include(['integer', 'text']);
                expect(item.relationships).to.not.exist();
            });
            done();
        });
    });

    it('should include all relations of a collection', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?include=1`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.length).to.equal(5);
            expect(included.map((item) => item.id)).to.only.include([
                'relation0',
                'relation1',
                'relation2',
                'relation3',
                'relation4'
            ]);
            done();
        });
    });

    it('should include a specified relation of a collection', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?include=relation`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.length).to.equal(2);
            expect(included.map((item) => item.id)).to.only.include([
                'relation0', 'relation1']);
            done();
        });
    });

    it('should include multiple specified relations of a collection', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?limit=1&include=relation,relations`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.length).to.equal(3);
            expect(included.map((item) => item.id)).to.only.include([
                'relation0', 'relation1', 'relation2']);
            done();
        });
    });

    it('should include all relations of a document', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[id]=generic1&include=1`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);
            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.map((item) => item.id)).to.only.include([
                'relation0', 'relation1', 'relation2']);
            done();
        });
    });

    it('should allow $in operator', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[integer][$in]=[2,4,6]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(3);
            data.forEach(function(item) {
                expect(item.attributes.integer).to.satisfy((num) => [2, 4, 6].indexOf(num) > -1);
            });

            done();
        });
    });

    it('should query relations', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[relation.text]=relation 1`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(5);
            data.forEach(function(item) {
                expect(item.attributes.integer).to.satisfy((num) => [1, 3, 7, 5, 9].indexOf(num) > -1);
            });

            done();
        });
    });

    it('should return an error if a field in query is not specified in schema', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[unknwonField]=3`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('unknown property "unknwonField" on model "Generic"');

            done();
        });
    });

    it('should return an error when query with a bad operator', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[integer][$arf]=3`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('unknown operator "$arf"');

            done();
        });
    });

    it('should return an error when query with a bad type', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[integer]=bla&filter[boolean]=arf`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('"integer" must be a number');

            done();
        });
    });

    it('should return an error when query with a bad relation type', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[relation.related]=bla`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('"related" must be a boolean');

            done();
        });

    });

    it('should return an error when specified a bad property in array', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?fields=["boolean","unknownProperty"]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('unknown property "unknownProperty" on model "Generic"');

            done();
        });
    });

    it('should return an error when specified a bad property as string', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?fields=boolean,unknownProperty`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('unknown property "unknownProperty" on model "Generic"');

            done();
        });
    });

    it('should return an error when sorting with an unknown property as array', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?sort=["boolean","unknownProperty"]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('unknown property "unknownProperty" on model "Generic"');

            done();
        });

    });

    it('should return an error when sorting with an unknown property as string', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?sort=boolean,unknownProperty`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('unknown property "unknownProperty" on model "Generic"');

            done();
        });

    });

});
<commit_msg>fix tests to match new error<commit_after>
import Lab from 'lab';
var lab = exports.lab = Lab.script();

import Code from 'code';
var describe = lab.describe;
var it = lab.it;
var beforeEach = lab.beforeEach;
var expect = Code.expect;

import eureka from '../../lib';
import config from '../config';
import fixtures from '../utils/fixtures';

const jsonApiMime = 'application/vnd.api+json';

describe('Route [find]', function() {

    /** load the server **/
    var server;
    lab.before(function(done) {
        eureka(config).compose(function(err, s) {
            expect(err).to.not.exists();
            server = s;
            done();
        });
    });

     /** load the fixtures **/
    beforeEach(function(done){
        fixtures.clear(server).then(() => {
            return fixtures.genericDocuments(server);
        }).then(() => {
            done();
        }).catch((error) => {
            console.log(error);
        });
    });

    it('should return all documents', function(done){
        let options = {
            method: 'GET',
            url: '/api/1/generics'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(10);
            expect(data[0].type).to.equal('Generic');

            done();
        });
    });

    it('should filter by id', function(done){
        let options = {
            method: 'GET',
            url: '/api/1/generics?filter[id]=generic3'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(1);
            expect(data[0].id).to.equal('generic3');
            expect(data[0].type).to.equal('Generic');
            expect(data[0].attributes).to.exist();
            done();
        });
    });

    it('should filter by ids', function(done){
        let options = {
            method: 'GET',
            url: '/api/1/generics?filter[id][$in]=["generic3","generic2"]'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(2);
            expect(data[0].id).to.equal('generic3');
            expect(data[0].attributes).to.exist();
            expect(data[1].id).to.equal('generic2');
            expect(data[1].attributes).to.exist();
            done();
        });
    });

    it('should sort the documents', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?sort=-integer`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(10);
            expect(data.map((o) => {
                return o.attributes.integer;
            })).to.deep.equal([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
            done();
        });
    });

    it('should limit the documents', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?limit=5`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(5);
            done();
        });
    });

    it('should return only the specified fields', function(done){
        let options = {
            method: 'GET',
            url: `/api/1/generics?fields=["integer","text"]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let data = response.result.data;
            expect(data).to.be.an.array();
            data.forEach(function(item) {
                expect(item.attributes).to.only.include(['integer', 'text']);
                expect(item.relationships).to.not.exist();
            });
            done();
        });
    });

    it('should include all relations of a collection', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?include=1`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.length).to.equal(5);
            expect(included.map((item) => item.id)).to.only.include([
                'relation0',
                'relation1',
                'relation2',
                'relation3',
                'relation4'
            ]);
            done();
        });
    });

    it('should include a specified relation of a collection', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?include=relation`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.length).to.equal(2);
            expect(included.map((item) => item.id)).to.only.include([
                'relation0', 'relation1']);
            done();
        });
    });

    it('should include multiple specified relations of a collection', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?limit=1&include=relation,relations`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.length).to.equal(3);
            expect(included.map((item) => item.id)).to.only.include([
                'relation0', 'relation1', 'relation2']);
            done();
        });
    });

    it('should include all relations of a document', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[id]=generic1&include=1`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);
            var included = response.result.included;
            expect(included).to.be.an.array();
            expect(included.map((item) => item.id)).to.only.include([
                'relation0', 'relation1', 'relation2']);
            done();
        });
    });

    it('should allow $in operator', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[integer][$in]=[2,4,6]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(3);
            data.forEach(function(item) {
                expect(item.attributes.integer).to.satisfy((num) => [2, 4, 6].indexOf(num) > -1);
            });

            done();
        });
    });

    it('should query relations', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[relation.text]=relation 1`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            var data = response.result.data;
            expect(data).to.be.an.array();
            expect(data.length).to.be.equal(5);
            data.forEach(function(item) {
                expect(item.attributes.integer).to.satisfy((num) => [1, 3, 7, 5, 9].indexOf(num) > -1);
            });

            done();
        });
    });

    it('should return an error if a field in query is not specified in schema', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[unknwonField]=3`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('unknown property "unknwonField" on model "Generic"');

            done();
        });
    });

    it('should return an error when query with a bad operator', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[integer][$arf]=3`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('unknown operator "$arf"');

            done();
        });
    });

    it('should return an error when query with a bad type', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[integer]=bla&filter[boolean]=arf`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('"integer" must be a number');

            done();
        });
    });

    it('should return an error when query with a bad relation type', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?filter[relation.related]=bla`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed query');
            expect(error.meta.infos).to.equal('"related" must be a boolean');

            done();
        });

    });

    it('should return an error when specified a bad property in array', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?fields=["boolean","unknownProperty"]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('fields: unknown property "unknownProperty" on model "Generic"');

            done();
        });
    });

    it('should return an error when specified a bad property as string', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?fields=boolean,unknownProperty`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('fields: unknown property "unknownProperty" on model "Generic"');

            done();
        });
    });

    it('should return an error when sorting with an unknown property as array', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?sort=["boolean","unknownProperty"]`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('sort: unknown property "unknownProperty" on model "Generic"');

            done();
        });

    });

    it('should return an error when sorting with an unknown property as string', function(done){

        let options = {
            method: 'GET',
            url: `/api/1/generics?sort=boolean,unknownProperty`
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(400);
            expect(response.headers['content-type']).to.include(jsonApiMime);

            let error = response.result.errors[0];
            expect(error.status).to.equal(400);
            expect(error.title).to.equal('Bad Request');
            expect(error.detail).to.equal('ValidationError: malformed options');
            expect(error.meta.infos).to.equal('sort: unknown property "unknownProperty" on model "Generic"');

            done();
        });

    });

});
<|endoftext|>


<commit_before>'use strict';

const async = require('async');
const createWebhookHandler = require('github-webhook-handler');
const requestLogger = require('probo-request-logger');
const restify = require('restify');

const API = require('./api');
const GitHub = require('./GitHub');
const logger = require('./logger');

/**
 * Create a queue that only processes one task at a time.
 * A task is simply a function that takes a callback when it's done
 */
var statusUpdateQueue = async.queue(function worker(fn, cb) {
  fn(cb);
}, 1);

class GitHubHandler {

  constructor(config, log) {
    this.config = config;

    // Instantiate a logger instance.
    this.logger = log || logger.get('github-handler');

    this.github = new GitHub(this.config, this.logger);

    this.webhookOptions = {
      path: config.githubWebhookPath,
      secret: config.githubWebhookSecret,
    };

    // Sets up the path for the GitHub webhooks.
    this.handler = this._createWebhookHandler();

    this.server = restify.createServer({log: this.logger, name: 'Probo GHH'});

    // Set ups the server and routes for the Probo GitLab Handler.
    this._setupServer();
    this._setupRoutes();

    this.api = API.getAPI({
      url: this.config.api.url,
      token: this.config.api.token,
      log: this.logger,
      // {url, [host|hostname], [protocol], [port]}
      handler: this.config,
    });

    if (!(this.api instanceof API)) {
      this.logger.info('api.token not found, using Container Manager API directly');
    }
  }

  /**
   * Starts the server.
   *
   * @param {() => void} cb - The callback function
   */
  start(cb) {
    this.server.listen({port: this.config.port, host: this.config.hostname || '0.0.0.0'}, () => {
      this.logger.info('Now listening on', this.server.url);

      if (cb) cb();
    });
  }

  /**
   * Closes the server.
   *
   * @param {() => void} cb - The callback function
   */
  close(cb) {
    const url = this.server.url;
    this.server.close(() => {
      this.logger.info('Stopped', url);

      if (cb) cb();
    });
  }

  /**
   * Creates a GitLab Webhook Handler.
   *
   * @return {import('github-webhook-handler')} - An initialized webhook handler server.
   */
  _createWebhookHandler() {
    let handler = createWebhookHandler(this.webhookOptions);

    handler.on('error', error => {
      this.logger.error({err: error}, 'An error occurred.');
    });

    handler.on('pull_request', this.pullRequestHandler.bind(this));

    return handler;
  }

  /**
   * Sets up the server for the Probo GitLab Handler.
   */
  _setupServer() {
    this.server.use(restify.plugins.queryParser());

    // Adds Probo's request logger
    this.server.use(requestLogger({logger: this.logger}));

    // Sets up request logging
    this.server.use((req, res, next) => {
      this.logger.info({req: req}, 'REQUEST');
      next();
    });

    this.server.on('after', restify.plugins.auditLogger({
      log: this.logger,
      event: 'after',
    }));
  }

  /**
   * Sets up the routes for the Probo GitLab Handler.
   *
   * These routes corresponds to the webhook handler and the status update
   * paths.
   */
  _setupRoutes() {
    // For requests to webhook handler path, make the webhook handler take care
    // of the requests.
    this.server.post(this.webhookOptions.path, (req, res, next) => {
      this.handler(req, res, error => {
        res.send(400, 'Error processing hook');
        this.logger.error({err: error}, 'Error processing hook');

        next();
      });
    });

    this.server.post('/builds/:bid/status/:context', restify.plugins.jsonBodyParser(), this.buildStatusController.bind(this));
    this.server.post('/update', restify.plugins.jsonBodyParser(), this.buildStatusController.bind(this));

    this.server.get('/pull-request/:owner/:repo/:pullRequestNumber', this.getPullRequest.bind(this));
  }

  /**
   * Called on a build status update event.
   *
   * @param {import('restify').Request} req - The request to the server.
   * @param {import('restify').Response} res - The server response
   * @param {import('restify').Next} next - The chain of handlers for the request.
   */
  buildStatusController(req, res, next) {
    const payload = req.body;

    if (req.params.context) {
      // Usually, context will already be part of update, but read it from URL
      // if it's there for compatability
      payload.update.context = req.params.context;
    }

    this.logger.debug({payload: payload}, 'Update payload');

    this.buildStatusUpdateHandler(payload.update, payload.build, (err, status) => {
      if (err) {
        res.send(500, {error: err});
      }
      else {
        res.send(status);
      }
      return next();
    });
  }

  /**
   * The handler for merge request events from GitHub webhooks.
   *
   * @param {Object.<string, any>} event - The merge request event.
   * @param {(err: Error, [res]) => void} cb cb - The callback to be called after the update is performed.
   */
  pullRequestHandler(event, cb) {
    this.logger.info('Github Pull request ' + event.payload.pull_request.id + ' received');

    // We only want to take action on open and synchronize events (for
    // now, we'll seen want to take action on close events as well).
    if (event.payload.action !== 'opened' && event.payload.action !== 'synchronize') {
      this.logger.info(`Github Pull request ${event.payload.pull_request.id} ${event.payload.action} ignored`);

      cb();
    }

    var request = {
      // Also in event.event.
      type: 'pull_request',
      service: 'github',
      branch: event.payload.pull_request.head.ref,
      branch_html_url: `${event.payload.repository.html_url}/tree/${event.payload.pull_request.head.ref}`,
      slug: event.payload.repository.full_name,
      owner: event.payload.repository.owner.login,
      repo: event.payload.repository.name,
      repo_id: event.payload.repository.id,
      sha: event.payload.pull_request.head.sha,
      commit_url: `${event.payload.repository.html_url}/commit/${event.payload.pull_request.head.sha}`,
      pull_request: event.payload.pull_request.number,
      pull_request_id: event.payload.pull_request.id,
      pull_request_name: event.payload.pull_request.title,
      pull_request_description: event.payload.pull_request.body,
      pull_request_html_url: event.payload.pull_request._links.html.href,
      payload: event.payload,
    };

    // Build comes back with an embedded .project key.
    this.processPullRequest(request, (error, build) => {
      this.logger.info({type: request.type, slug: request.slug, err: error}, 'request processed');
      if (cb) {
        cb(error, build);
      }
    });
  }

  /**
   * Update the status on GitHub.
   *
   * @param {Object.<string, any>} update - The update object.
   * @param {string} update.state: "status of build",
   * @param {string} update.description - The text discription of the build state.
   * @param {string} update.context - The context used to differentiate this update from other services and steps.
   * @param {string} update.target_url: The url to link to from the status update.
   * @param {Object.<string, any>} build - The full build object.
   * @param {Object.<string, any>} build.project - The embedded project object.
   * @param {(err: Error, [res]) => void} cb - The callback to be called after the update is performed.
   */
  buildStatusUpdateHandler(update, build, cb) {
    this.logger.info({update: update, build_id: build.id}, 'Got build status update');

    // Create a mapping of states that Github accepts
    const stateMap = {
      running: 'pending',
      pending: 'pending',
      success: 'success',
      error: 'failure',
    };

    const statusInfo = {
      // Can be one of pending, success, error, or failure.
      state: stateMap[update.state],
      description: update.description.substring(0, 140),
      context: update.context,
      target_url: update.target_url,
    };

    const task = this.github.postStatus.bind(this.github, build.project, build.commit.ref, statusInfo);
    statusUpdateQueue.push(task, error => {
      if (error) {
        this.logger.error({err: error, build_id: build.id}, 'An error occurred posting status to GitHub');
        return cb(error, statusInfo);
      }

      this.logger.info(statusInfo, 'Posted status to Github for', build.project.slug, build.commit.ref);
      cb(null, statusInfo);
    });
  }

  /**
   * Processes a pull request and submits a Probo build.
   *
   * @param {Object.<string, any>} request - The incoming hook request data.
   * @param {string} request.type - The type of request to process (eg pull_request).
   * @param {string} request.service - The service to be checked (always gitlab in this handler).
   * @param {string} request.slug - The identifier for the repo (repository.full_name from the gitlab api).
   * @param {string} request.event - The entire event payload from the gitlab api call.
   * @param {(err: Error, [res]) => void} cb - The callback to call when finished.
   */
  processPullRequest(request, cb) {
    this.logger.info({type: request.type, id: request.id}, 'Processing request');

    this.api.findProjectByRepo(request, (error, project) => {
      if (error || !project) {
        return this.logger.info({error}, `Project for github repo ${request.slug} not found`);
      }

      this.logger.info({project: project}, 'Found project for PR');

      this.github.fetchProboYamlConfig(project, request.sha, (error, config) => {
        let build;

        if (error) {
          this.logger.error({err: error}, 'Problem fetching Probo Yaml Config file');

          // If we can't find a yaml file we should error.
          build = {
            commit: {ref: request.sha},
            project: project,
          };
          const update = {
            state: 'failure',
            description: error.message,
            context: 'ProboCI/env',
          };

          return this.buildStatusUpdateHandler(update, build, cb);
        }

        this.logger.info({config: config}, 'Probo Yaml Config file');

        this.submitBuild(request, project, config, cb);
      });
    });
  }

  /**
   * Called on a get PR request. Returns a PR info.
   *
   * @param {import('restify').Request} req - The request to the server.
   * @param {import('restify').Response} res - The server response
   * @param {import('restify').Next} next - The chain of handlers for the request.
   */
  getPullRequest(req, res, next) {
    let opts = {
      owner: req.params.owner,
      repo: req.params.repo,
      pull_number: req.params.pullRequestNumber,
      token: req.query.token,
    };

    this.github.getPullRequest(opts)
      .then(pullRequest => {
        res.json(pullRequest);

        next();
      })
      .catch(err => {
        res.json(500, err);

        next(err);
      });
  }

  /**
   * Submits a Probo build request.
   *
   * @param {Object.<string, string>} request - Information on the repo/branch/commit to build.
   * @param {Object.<string, any>} project - The project to build.
   * @param {string} config - The probo YAML config file.
   * @param {(err: Error, [res]) => void} cb cb - The callback to call when finished.
   */
  submitBuild(request, project, config, cb) {
    let build = {
      commit: {
        ref: request.sha,
        htmlUrl: request.commit_url,
      },
      pullRequest: {
        number: request.pull_request + '',
        name: request.pull_request_name,
        description: request.pull_request_description,
        htmlUrl: request.pull_request_html_url,
      },
      branch: {
        name: request.branch,
        htmlUrl: request.branch_html_url,
      },
      config: config,
      request: request,
    };

    this.api.submitBuild(build, project, (err, submittedBuild) => {
      if (err) {
        // TODO: save the PR if submitting it fails (though logging it here might be ok)
        self.log.error({
          err: err,
          request: request,
          build: build,
          response: submittedBuild,
        }, 'Problem submitting build');

        return cb && cb(err);
      }

      this.logger.info({build: submittedBuild}, 'Submitted build');

      cb(null, submittedBuild);
    });
  }

}

module.exports = GitHubHandler;
<commit_msg>Set logging level based on the configuration<commit_after>'use strict';

const async = require('async');
const createWebhookHandler = require('github-webhook-handler');
const requestLogger = require('probo-request-logger');
const restify = require('restify');

const API = require('./api');
const GitHub = require('./GitHub');
const logger = require('./logger');

/**
 * Create a queue that only processes one task at a time.
 * A task is simply a function that takes a callback when it's done
 */
var statusUpdateQueue = async.queue(function worker(fn, cb) {
  fn(cb);
}, 1);

class GitHubHandler {

  constructor(config, log) {
    this.config = config;

    // Instantiate a logger instance.
    this.logger = log || logger.get('github-handler');
    this.logger.level(config.logLevel);

    this.github = new GitHub(this.config, this.logger);

    this.webhookOptions = {
      path: config.githubWebhookPath,
      secret: config.githubWebhookSecret,
    };

    // Sets up the path for the GitHub webhooks.
    this.handler = this._createWebhookHandler();

    this.server = restify.createServer({log: this.logger, name: 'Probo GHH'});

    // Set ups the server and routes for the Probo GitLab Handler.
    this._setupServer();
    this._setupRoutes();

    this.api = API.getAPI({
      url: this.config.api.url,
      token: this.config.api.token,
      log: this.logger,
      // {url, [host|hostname], [protocol], [port]}
      handler: this.config,
    });

    if (!(this.api instanceof API)) {
      this.logger.info('api.token not found, using Container Manager API directly');
    }
  }

  /**
   * Starts the server.
   *
   * @param {() => void} cb - The callback function
   */
  start(cb) {
    this.server.listen({port: this.config.port, host: this.config.hostname || '0.0.0.0'}, () => {
      this.logger.info('Now listening on', this.server.url);

      if (cb) cb();
    });
  }

  /**
   * Closes the server.
   *
   * @param {() => void} cb - The callback function
   */
  close(cb) {
    const url = this.server.url;
    this.server.close(() => {
      this.logger.info('Stopped', url);

      if (cb) cb();
    });
  }

  /**
   * Creates a GitLab Webhook Handler.
   *
   * @return {import('github-webhook-handler')} - An initialized webhook handler server.
   */
  _createWebhookHandler() {
    let handler = createWebhookHandler(this.webhookOptions);

    handler.on('error', error => {
      this.logger.error({err: error}, 'An error occurred.');
    });

    handler.on('pull_request', this.pullRequestHandler.bind(this));

    return handler;
  }

  /**
   * Sets up the server for the Probo GitLab Handler.
   */
  _setupServer() {
    this.server.use(restify.plugins.queryParser());

    // Adds Probo's request logger
    this.server.use(requestLogger({logger: this.logger}));

    // Sets up request logging
    this.server.use((req, res, next) => {
      this.logger.info({req: req}, 'REQUEST');
      next();
    });

    this.server.on('after', restify.plugins.auditLogger({
      log: this.logger,
      event: 'after',
    }));
  }

  /**
   * Sets up the routes for the Probo GitLab Handler.
   *
   * These routes corresponds to the webhook handler and the status update
   * paths.
   */
  _setupRoutes() {
    // For requests to webhook handler path, make the webhook handler take care
    // of the requests.
    this.server.post(this.webhookOptions.path, (req, res, next) => {
      this.handler(req, res, error => {
        res.send(400, 'Error processing hook');
        this.logger.error({err: error}, 'Error processing hook');

        next();
      });
    });

    this.server.post('/builds/:bid/status/:context', restify.plugins.jsonBodyParser(), this.buildStatusController.bind(this));
    this.server.post('/update', restify.plugins.jsonBodyParser(), this.buildStatusController.bind(this));

    this.server.get('/pull-request/:owner/:repo/:pullRequestNumber', this.getPullRequest.bind(this));
  }

  /**
   * Called on a build status update event.
   *
   * @param {import('restify').Request} req - The request to the server.
   * @param {import('restify').Response} res - The server response
   * @param {import('restify').Next} next - The chain of handlers for the request.
   */
  buildStatusController(req, res, next) {
    const payload = req.body;

    if (req.params.context) {
      // Usually, context will already be part of update, but read it from URL
      // if it's there for compatability
      payload.update.context = req.params.context;
    }

    this.logger.debug({payload: payload}, 'Update payload');

    this.buildStatusUpdateHandler(payload.update, payload.build, (err, status) => {
      if (err) {
        res.send(500, {error: err});
      }
      else {
        res.send(status);
      }
      return next();
    });
  }

  /**
   * The handler for merge request events from GitHub webhooks.
   *
   * @param {Object.<string, any>} event - The merge request event.
   * @param {(err: Error, [res]) => void} cb cb - The callback to be called after the update is performed.
   */
  pullRequestHandler(event, cb) {
    this.logger.info('Github Pull request ' + event.payload.pull_request.id + ' received');

    // We only want to take action on open and synchronize events (for
    // now, we'll seen want to take action on close events as well).
    if (event.payload.action !== 'opened' && event.payload.action !== 'synchronize') {
      this.logger.info(`Github Pull request ${event.payload.pull_request.id} ${event.payload.action} ignored`);

      cb();
    }

    var request = {
      // Also in event.event.
      type: 'pull_request',
      service: 'github',
      branch: event.payload.pull_request.head.ref,
      branch_html_url: `${event.payload.repository.html_url}/tree/${event.payload.pull_request.head.ref}`,
      slug: event.payload.repository.full_name,
      owner: event.payload.repository.owner.login,
      repo: event.payload.repository.name,
      repo_id: event.payload.repository.id,
      sha: event.payload.pull_request.head.sha,
      commit_url: `${event.payload.repository.html_url}/commit/${event.payload.pull_request.head.sha}`,
      pull_request: event.payload.pull_request.number,
      pull_request_id: event.payload.pull_request.id,
      pull_request_name: event.payload.pull_request.title,
      pull_request_description: event.payload.pull_request.body,
      pull_request_html_url: event.payload.pull_request._links.html.href,
      payload: event.payload,
    };

    // Build comes back with an embedded .project key.
    this.processPullRequest(request, (error, build) => {
      this.logger.info({type: request.type, slug: request.slug, err: error}, 'request processed');
      if (cb) {
        cb(error, build);
      }
    });
  }

  /**
   * Update the status on GitHub.
   *
   * @param {Object.<string, any>} update - The update object.
   * @param {string} update.state: "status of build",
   * @param {string} update.description - The text discription of the build state.
   * @param {string} update.context - The context used to differentiate this update from other services and steps.
   * @param {string} update.target_url: The url to link to from the status update.
   * @param {Object.<string, any>} build - The full build object.
   * @param {Object.<string, any>} build.project - The embedded project object.
   * @param {(err: Error, [res]) => void} cb - The callback to be called after the update is performed.
   */
  buildStatusUpdateHandler(update, build, cb) {
    this.logger.info({update: update, build_id: build.id}, 'Got build status update');

    // Create a mapping of states that Github accepts
    const stateMap = {
      running: 'pending',
      pending: 'pending',
      success: 'success',
      error: 'failure',
    };

    const statusInfo = {
      // Can be one of pending, success, error, or failure.
      state: stateMap[update.state],
      description: update.description.substring(0, 140),
      context: update.context,
      target_url: update.target_url,
    };

    const task = this.github.postStatus.bind(this.github, build.project, build.commit.ref, statusInfo);
    statusUpdateQueue.push(task, error => {
      if (error) {
        this.logger.error({err: error, build_id: build.id}, 'An error occurred posting status to GitHub');
        return cb(error, statusInfo);
      }

      this.logger.info(statusInfo, 'Posted status to Github for', build.project.slug, build.commit.ref);
      cb(null, statusInfo);
    });
  }

  /**
   * Processes a pull request and submits a Probo build.
   *
   * @param {Object.<string, any>} request - The incoming hook request data.
   * @param {string} request.type - The type of request to process (eg pull_request).
   * @param {string} request.service - The service to be checked (always gitlab in this handler).
   * @param {string} request.slug - The identifier for the repo (repository.full_name from the gitlab api).
   * @param {string} request.event - The entire event payload from the gitlab api call.
   * @param {(err: Error, [res]) => void} cb - The callback to call when finished.
   */
  processPullRequest(request, cb) {
    this.logger.info({type: request.type, id: request.id}, 'Processing request');

    this.api.findProjectByRepo(request, (error, project) => {
      if (error || !project) {
        return this.logger.info({error}, `Project for github repo ${request.slug} not found`);
      }

      this.logger.info({project: project}, 'Found project for PR');

      this.github.fetchProboYamlConfig(project, request.sha, (error, config) => {
        let build;

        if (error) {
          this.logger.error({err: error}, 'Problem fetching Probo Yaml Config file');

          // If we can't find a yaml file we should error.
          build = {
            commit: {ref: request.sha},
            project: project,
          };
          const update = {
            state: 'failure',
            description: error.message,
            context: 'ProboCI/env',
          };

          return this.buildStatusUpdateHandler(update, build, cb);
        }

        this.logger.info({config: config}, 'Probo Yaml Config file');

        this.submitBuild(request, project, config, cb);
      });
    });
  }

  /**
   * Called on a get PR request. Returns a PR info.
   *
   * @param {import('restify').Request} req - The request to the server.
   * @param {import('restify').Response} res - The server response
   * @param {import('restify').Next} next - The chain of handlers for the request.
   */
  getPullRequest(req, res, next) {
    let opts = {
      owner: req.params.owner,
      repo: req.params.repo,
      pull_number: req.params.pullRequestNumber,
      token: req.query.token,
    };

    this.github.getPullRequest(opts)
      .then(pullRequest => {
        res.json(pullRequest);

        next();
      })
      .catch(err => {
        res.json(500, err);

        next(err);
      });
  }

  /**
   * Submits a Probo build request.
   *
   * @param {Object.<string, string>} request - Information on the repo/branch/commit to build.
   * @param {Object.<string, any>} project - The project to build.
   * @param {string} config - The probo YAML config file.
   * @param {(err: Error, [res]) => void} cb cb - The callback to call when finished.
   */
  submitBuild(request, project, config, cb) {
    let build = {
      commit: {
        ref: request.sha,
        htmlUrl: request.commit_url,
      },
      pullRequest: {
        number: request.pull_request + '',
        name: request.pull_request_name,
        description: request.pull_request_description,
        htmlUrl: request.pull_request_html_url,
      },
      branch: {
        name: request.branch,
        htmlUrl: request.branch_html_url,
      },
      config: config,
      request: request,
    };

    this.api.submitBuild(build, project, (err, submittedBuild) => {
      if (err) {
        // TODO: save the PR if submitting it fails (though logging it here might be ok)
        self.log.error({
          err: err,
          request: request,
          build: build,
          response: submittedBuild,
        }, 'Problem submitting build');

        return cb && cb(err);
      }

      this.logger.info({build: submittedBuild}, 'Submitted build');

      cb(null, submittedBuild);
    });
  }

}

module.exports = GitHubHandler;
<|endoftext|>


<commit_before>describe("Map", function () {
	var map,
		spy;
	beforeEach(function () {
		map = L.map(document.createElement('div'));
		spy = jasmine.createSpy();
	});

	describe('#getCenter', function () {
		it ('should throw if not set before', function () {
			expect(function () {
				map.getCenter();
			}).toThrow();
		});
	});

	describe("#whenReady", function () {
		describe("when the map has not yet been loaded", function () {
			it("calls the callback when the map is loaded", function () {
				map.whenReady(spy);
				expect(spy).not.toHaveBeenCalled();

				map.setView([0, 0], 1);
				expect(spy).toHaveBeenCalled();
			});
		});

		describe("when the map has already been loaded", function () {
			it("calls the callback immediately", function () {
				map.setView([0, 0], 1);
				map.whenReady(spy);

				expect(spy).toHaveBeenCalled();
			});
		});
	});

	describe("#getBounds", function () {
		it("is safe to call from within a moveend callback during initial " +
		   "load (#1027)", function () {
			map.on("moveend", function () {
			  map.getBounds();
			});

			map.setView([51.505, -0.09], 13);
		});
	});

	describe("#getMinZoom and #getMaxZoom", function () {
		it("The minZoom and maxZoom options overrides any" +
		   " minZoom and maxZoom set on layers", function () {
			   var c = document.createElement('div'),
				   map = L.map(c, { minZoom: 5, maxZoom: 10 });
			   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
			   L.tileLayer("{z}{x}{y}", { minZoom:5, maxZoom: 15 }).addTo(map);
			   expect(map.getMinZoom()).toBe(5);
			   expect(map.getMaxZoom()).toBe(10);
		   });
	});

	describe("#addLayer", function () {
		describe("When the first layer is added to a map", function () {
			it("should fire a zoomlevelschange event", function () {
				map.on("zoomlevelschange", spy);
				expect(spy).not.toHaveBeenCalled();
				L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				expect(spy).toHaveBeenCalled();
			});
		});
		describe("when a new layer with greater zoomlevel coverage than the current layer is added to a map", function () {
			it("Should fire a zoomlevelschange event ",
			   function () {
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				   map.on("zoomlevelschange", spy);
				   expect(spy).not.toHaveBeenCalled();
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 }).addTo(map);
				   expect(spy).toHaveBeenCalled();
			   });
		});
		describe("when a new layer with the same or lower zoomlevel coverage as the current layer is added to a map", function () {
			it("Shouldn't fire a zoomlevelschange event ",
			   function () {
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				   map.on("zoomlevelschange", spy);
				   expect(spy).not.toHaveBeenCalled();
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				   expect(spy).not.toHaveBeenCalled();
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 }).addTo(map);
				   expect(spy).not.toHaveBeenCalled();

			   });
		});

	});
	describe("#removeLayer", function () {
		describe("when the last tile layer on a map is removed", function () {
			it("should fire a zoomlevelschange event ", function () {
				   map.whenReady(function(){
					   var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							   .addTo(map);

					   map.on("zoomlevelschange", spy);
					   expect(spy).not.toHaveBeenCalled();
					   map.removeLayer(tl);
					   expect(spy).toHaveBeenCalled();
				   });
			   });
		});
		describe("when a tile layer is removed from a map and it had greater zoom level coverage than the remainding layer", function () {
			it("should fire a zoomlevelschange event ", function () {
				   map.whenReady(function(){
					   var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							   .addTo(map),
						   t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 })
							   .addTo(map);

					   map.on("zoomlevelschange", spy);
					   expect(spy).not.toHaveBeenCalled();
					   map.removeLayer(t2);
					   expect(spy).toHaveBeenCalled();
				   });
			});
		});
		describe("when a tile layer is removed from a map it and it had lesser or the sa,e zoom level coverage as the remainding layer(s)", function () {
			it("shouldn't fire a zoomlevelschange event ", function () {
				map.whenReady(function(){
					var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							.addTo(map),
						t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							.addTo(map),
						t3 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 })
							.addTo(map);

					map.on("zoomlevelschange", spy);
					expect(spy).not.toHaveBeenCalled();
					map.removeLayer(t2);
					expect(spy).not.toHaveBeenCalled();
					map.removeLayer(t3);
					expect(spy).not.toHaveBeenCalled();

				});
			});
		});

	});
});
<commit_msg>Test setView. The setTimeout can be removed when #1420 is fixed.<commit_after>describe("Map", function () {
	var map,
		spy;
	beforeEach(function () {
		map = L.map(document.createElement('div'));
		spy = jasmine.createSpy();
	});

	describe('#getCenter', function () {
		it ('should throw if not set before', function () {
			expect(function () {
				map.getCenter();
			}).toThrow();
		});
	});

	describe("#whenReady", function () {
		describe("when the map has not yet been loaded", function () {
			it("calls the callback when the map is loaded", function () {
				map.whenReady(spy);
				expect(spy).not.toHaveBeenCalled();

				map.setView([0, 0], 1);
				expect(spy).toHaveBeenCalled();
			});
		});

		describe("when the map has already been loaded", function () {
			it("calls the callback immediately", function () {
				map.setView([0, 0], 1);
				map.whenReady(spy);

				expect(spy).toHaveBeenCalled();
			});
		});
	});

	describe("#setView", function () {
		it("sets the view of the map", function (done) {
			expect(map.setView([51.505, -0.09], 13)).toBe(map);
            expect(map.getZoom()).toBe(13);

            window.setTimeout(function() {
                expect(map.getCenter().distanceTo([51.505, -0.09])).toBeLessThan(1);
                done();
            }, 500);
		});
	});

	describe("#getBounds", function () {
		it("is safe to call from within a moveend callback during initial " +
		   "load (#1027)", function () {
			map.on("moveend", function () {
			  map.getBounds();
			});

			map.setView([51.505, -0.09], 13);
		});
	});

	describe("#getMinZoom and #getMaxZoom", function () {
		it("The minZoom and maxZoom options overrides any" +
		   " minZoom and maxZoom set on layers", function () {
			   var c = document.createElement('div'),
				   map = L.map(c, { minZoom: 5, maxZoom: 10 });
			   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
			   L.tileLayer("{z}{x}{y}", { minZoom:5, maxZoom: 15 }).addTo(map);
			   expect(map.getMinZoom()).toBe(5);
			   expect(map.getMaxZoom()).toBe(10);
		   });
	});

	describe("#addLayer", function () {
		describe("When the first layer is added to a map", function () {
			it("should fire a zoomlevelschange event", function () {
				map.on("zoomlevelschange", spy);
				expect(spy).not.toHaveBeenCalled();
				L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				expect(spy).toHaveBeenCalled();
			});
		});
		describe("when a new layer with greater zoomlevel coverage than the current layer is added to a map", function () {
			it("Should fire a zoomlevelschange event ",
			   function () {
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				   map.on("zoomlevelschange", spy);
				   expect(spy).not.toHaveBeenCalled();
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 }).addTo(map);
				   expect(spy).toHaveBeenCalled();
			   });
		});
		describe("when a new layer with the same or lower zoomlevel coverage as the current layer is added to a map", function () {
			it("Shouldn't fire a zoomlevelschange event ",
			   function () {
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				   map.on("zoomlevelschange", spy);
				   expect(spy).not.toHaveBeenCalled();
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
				   expect(spy).not.toHaveBeenCalled();
				   L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 }).addTo(map);
				   expect(spy).not.toHaveBeenCalled();

			   });
		});

	});
	describe("#removeLayer", function () {
		describe("when the last tile layer on a map is removed", function () {
			it("should fire a zoomlevelschange event ", function () {
				   map.whenReady(function(){
					   var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							   .addTo(map);

					   map.on("zoomlevelschange", spy);
					   expect(spy).not.toHaveBeenCalled();
					   map.removeLayer(tl);
					   expect(spy).toHaveBeenCalled();
				   });
			   });
		});
		describe("when a tile layer is removed from a map and it had greater zoom level coverage than the remainding layer", function () {
			it("should fire a zoomlevelschange event ", function () {
				   map.whenReady(function(){
					   var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							   .addTo(map),
						   t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 })
							   .addTo(map);

					   map.on("zoomlevelschange", spy);
					   expect(spy).not.toHaveBeenCalled();
					   map.removeLayer(t2);
					   expect(spy).toHaveBeenCalled();
				   });
			});
		});
		describe("when a tile layer is removed from a map it and it had lesser or the sa,e zoom level coverage as the remainding layer(s)", function () {
			it("shouldn't fire a zoomlevelschange event ", function () {
				map.whenReady(function(){
					var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							.addTo(map),
						t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 })
							.addTo(map),
						t3 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 })
							.addTo(map);

					map.on("zoomlevelschange", spy);
					expect(spy).not.toHaveBeenCalled();
					map.removeLayer(t2);
					expect(spy).not.toHaveBeenCalled();
					map.removeLayer(t3);
					expect(spy).not.toHaveBeenCalled();

				});
			});
		});

	});
});
<|endoftext|>


<commit_before>specialForms.set = (args, scope) => {
    if (args.length != 2 || args[0].type != "word") {
        throw new SyntaxError("Incorrect use of set");
    }
    let curScope = scope;
    while (!Object.prototype.hasOwnProperty.call(curScope, args[0].name)) {
        curScope = Object.getPrototypeOf(curScope);
        if (curScope === null) {
            throw new ReferenceError("Can't set non-existing binding " +
                                     `'${args[0].name}'`);
        }
    }
    let value = evaluate(args[1], scope);
    curScope[args[0].name] = value;
    return value;
}
<commit_msg>Add review comments<commit_after>specialForms.set = (args, scope) => {
    if (args.length != 2 || args[0].type != "word") {
        throw new SyntaxError("Incorrect use of set");
    }
    let curScope = scope;

    // Would be nicer with a for loop to change scope
    while (!Object.prototype.hasOwnProperty.call(curScope, args[0].name)) {
        curScope = Object.getPrototypeOf(curScope);

        // Could probably use (!curScope) instead
        if (curScope === null) {
            throw new ReferenceError("Can't set non-existing binding " +
                                     `'${args[0].name}'`);
        }
    }
    let value = evaluate(args[1], scope);
    curScope[args[0].name] = value;
    return value;
}
<|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const compareLocations = require("./compareLocations");
let debugId = 1000;
const removeAndDo = require("./removeAndDo");

const byId = (a, b) => {
	if(a.id < b.id) return -1;
	if(b.id < a.id) return 1;
	return 0;
};

class Chunk {

	constructor(name, module, loc) {
		this.id = null;
		this.ids = null;
		this.debugId = debugId++;
		this.name = name;
		this.modules = [];
		this.entrypoints = [];
		this.chunks = [];
		this.parents = [];
		this.blocks = [];
		this.origins = [];
		this.files = [];
		this.rendered = false;
		if(module) {
			this.origins.push({
				module,
				loc,
				name
			});
		}
	}

	get entry() {
		throw new Error("Chunk.entry was removed. Use hasRuntime()");
	}

	set entry(data) {
		throw new Error("Chunk.entry was removed. Use hasRuntime()");
	}

	get initial() {
		throw new Error("Chunk.initial was removed. Use isInitial()");
	}

	set initial(data) {
		throw new Error("Chunk.initial was removed. Use isInitial()");
	}

	hasRuntime() {
		if(this.entrypoints.length === 0) return false;
		return this.entrypoints[0].chunks[0] === this;
	}

	isInitial() {
		return this.entrypoints.length > 0;
	}

	hasEntryModule() {
		return !!this.entryModule;
	}

	addToCollection(collection, item) {
		if(item === this) {
			return false;
		}

		if(collection.indexOf(item) > -1) {
			return false;
		}

		collection.push(item);
		return true;
	}

	addChunk(chunk) {
		return this.addToCollection(this.chunks, chunk);
	}

	addParent(parentChunk) {
		return this.addToCollection(this.parents, parentChunk);
	}

	addModule(module) {
		return this.addToCollection(this.modules, module);
	}

	addBlock(block) {
		return this.addToCollection(this.blocks, block);
	}

	removeModule(module) {
		removeAndDo(this.modules, module, "removeChunk", this);
	}

	removeChunk(chunk) {
		removeAndDo(this.chunks, chunk, "removeParent", this);
	}

	removeParent(chunk) {
		removeAndDo(this.parents, chunk, "removeChunk", this);
	}

	addOrigin(module, loc) {
		this.origins.push({
			module,
			loc,
			name: this.name
		});
	}

	remove(reason) {
		// cleanup modules
		this.modules.slice().forEach(module => {
			module.removeChunk(this);
		});

		// cleanup parents
		this.parents.forEach(parentChunk => {
			// remove this chunk from its parents
			const idx = parentChunk.chunks.indexOf(this);
			if(idx >= 0) {
				parentChunk.chunks.splice(idx, 1);
			}

			// cleanup "sub chunks"
			this.chunks.forEach(chunk => {
				/**
				 * remove this chunk as "intermediary" and connect
				 * it "sub chunks" and parents directly
				 */
				// add parent to each "sub chunk"
				chunk.addParent(parentChunk);
				// add "sub chunk" to parent
				parentChunk.addChunk(chunk);

				// remove this as parent of every "sub chunk"
				const idx = chunk.parents.indexOf(this);
				if(idx >= 0) {
					chunk.parents.splice(idx, 1);
				}
			});
		});

		// cleanup blocks
		this.blocks.forEach(block => {
			const idx = block.chunks.indexOf(this);
			if(idx >= 0) {
				block.chunks.splice(idx, 1);
				if(block.chunks.length === 0) {
					block.chunks = null;
					block.chunkReason = reason;
				}
			}
		});
	}

	moveModule(module, otherChunk) {
		module.removeChunk(this);
		module.addChunk(otherChunk);
		otherChunk.addModule(module);
		module.rewriteChunkInReasons(this, [otherChunk]);
	}

	replaceChunk(oldChunk, newChunk) {
		const idx = this.chunks.indexOf(oldChunk);
		if(idx >= 0) {
			this.chunks.splice(idx, 1);
		}
		if(this !== newChunk && newChunk.addParent(this)) {
			this.addChunk(newChunk);
		}
	}

	replaceParentChunk(oldParentChunk, newParentChunk) {
		const idx = this.parents.indexOf(oldParentChunk);
		if(idx >= 0) {
			this.parents.splice(idx, 1);
		}
		if(this !== newParentChunk && newParentChunk.addChunk(this)) {
			this.addParent(newParentChunk);
		}
	}

	integrate(otherChunk, reason) {
		if(!this.canBeIntegrated(otherChunk)) {
			return false;
		}

		const otherChunkModules = otherChunk.modules.slice();
		otherChunkModules.forEach(module => otherChunk.moveModule(module, this));
		otherChunk.modules.length = 0;

		otherChunk.parents.forEach(parentChunk => parentChunk.replaceChunk(otherChunk, this));
		otherChunk.parents.length = 0;

		otherChunk.chunks.forEach(chunk => chunk.replaceParentChunk(otherChunk, this));
		otherChunk.chunks.length = 0;

		otherChunk.blocks.forEach(b => {
			b.chunks = b.chunks ? b.chunks.map(c => {
				return c === otherChunk ? this : c;
			}) : [this];
			b.chunkReason = reason;
			this.addBlock(b);
		});
		otherChunk.blocks.length = 0;

		otherChunk.origins.forEach(origin => {
			this.origins.push(origin);
		});
		this.origins.forEach(origin => {
			if(!origin.reasons) {
				origin.reasons = [reason];
			} else if(origin.reasons[0] !== reason) {
				origin.reasons.unshift(reason);
			}
		});
		this.chunks = this.chunks.filter(chunk => {
			return chunk !== otherChunk && chunk !== this;
		});
		this.parents = this.parents.filter(parentChunk => {
			return parentChunk !== otherChunk && parentChunk !== this;
		});
		return true;
	}

	split(newChunk) {
		this.blocks.forEach(block => {
			newChunk.blocks.push(block);
			block.chunks.push(newChunk);
		});
		this.chunks.forEach(chunk => {
			newChunk.chunks.push(chunk);
			chunk.parents.push(newChunk);
		});
		this.parents.forEach(parentChunk => {
			parentChunk.chunks.push(newChunk);
			newChunk.parents.push(parentChunk);
		});
		this.entrypoints.forEach(entrypoint => {
			entrypoint.insertChunk(newChunk, this);
		});
	}

	isEmpty() {
		return this.modules.length === 0;
	}

	updateHash(hash) {
		hash.update(`${this.id} `);
		hash.update(this.ids ? this.ids.join(",") : "");
		hash.update(`${this.name || ""} `);
		this.modules.forEach(m => m.updateHash(hash));
	}

	canBeIntegrated(other) {
		if(other.isInitial()) {
			return false;
		}
		if(this.isInitial()) {
			if(other.parents.length !== 1 || other.parents[0] !== this) {
				return false;
			}
		}
		return true;
	}

	addMultiplierAndOverhead(size, options) {
		const overhead = typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000;
		const multiplicator = this.isInitial() ? (options.entryChunkMultiplicator || 10) : 1;

		return size * multiplicator + overhead;
	}

	modulesSize() {
		let count = 0;
		for(let i = 0; i < this.modules.length; i++) {
			count += this.modules[i].size();
		}
		return count;
	}

	size(options) {
		return this.addMultiplierAndOverhead(this.modulesSize(), options);
	}

	integratedSize(other, options) {
		// Chunk if it's possible to integrate this chunk
		if(!this.canBeIntegrated(other)) {
			return false;
		}

		const modulesSize = this.modulesSize() + other.modulesSize();
		return this.addMultiplierAndOverhead(modulesSize, options);
	}

	getChunkMaps(includeEntries, realHash) {
		const chunksProcessed = [];
		const chunkHashMap = {};
		const chunkNameMap = {};
		(function addChunk(chunk) {
			if(chunksProcessed.indexOf(chunk) >= 0) return;
			chunksProcessed.push(chunk);
			if(!chunk.hasRuntime() || includeEntries) {
				chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
				if(chunk.name)
					chunkNameMap[chunk.id] = chunk.name;
			}
			chunk.chunks.forEach(addChunk);
		}(this));
		return {
			hash: chunkHashMap,
			name: chunkNameMap
		};
	}

	sortItems() {
		this.modules.sort(byId);
		this.origins.sort((a, b) => {
			const aIdent = a.module.identifier();
			const bIdent = b.module.identifier();
			if(aIdent < bIdent) return -1;
			if(aIdent > bIdent) return 1;
			return compareLocations(a.loc, b.loc);
		});
		this.origins.forEach(origin => {
			if(origin.reasons)
				origin.reasons.sort();
		});
		this.parents.sort(byId);
		this.chunks.sort(byId);
	}

	toString() {
		return `Chunk[${this.modules.join()}]`;
	}

	checkConstraints() {
		const chunk = this;
		chunk.chunks.forEach((child, idx) => {
			if(chunk.chunks.indexOf(child) !== idx)
				throw new Error(`checkConstraints: duplicate child in chunk ${chunk.debugId} ${child.debugId}`);
			if(child.parents.indexOf(chunk) < 0)
				throw new Error(`checkConstraints: child missing parent ${chunk.debugId} -> ${child.debugId}`);
		});
		chunk.parents.forEach((parentChunk, idx) => {
			if(chunk.parents.indexOf(parentChunk) !== idx)
				throw new Error(`checkConstraints: duplicate parent in chunk ${chunk.debugId} ${parentChunk.debugId}`);
			if(parentChunk.chunks.indexOf(chunk) < 0)
				throw new Error(`checkConstraints: parent missing child ${parentChunk.debugId} <- ${chunk.debugId}`);
		});
	}
}

module.exports = Chunk;
<commit_msg>rename other to otherChunk<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";

const compareLocations = require("./compareLocations");
let debugId = 1000;
const removeAndDo = require("./removeAndDo");

const byId = (a, b) => {
	if(a.id < b.id) return -1;
	if(b.id < a.id) return 1;
	return 0;
};

class Chunk {

	constructor(name, module, loc) {
		this.id = null;
		this.ids = null;
		this.debugId = debugId++;
		this.name = name;
		this.modules = [];
		this.entrypoints = [];
		this.chunks = [];
		this.parents = [];
		this.blocks = [];
		this.origins = [];
		this.files = [];
		this.rendered = false;
		if(module) {
			this.origins.push({
				module,
				loc,
				name
			});
		}
	}

	get entry() {
		throw new Error("Chunk.entry was removed. Use hasRuntime()");
	}

	set entry(data) {
		throw new Error("Chunk.entry was removed. Use hasRuntime()");
	}

	get initial() {
		throw new Error("Chunk.initial was removed. Use isInitial()");
	}

	set initial(data) {
		throw new Error("Chunk.initial was removed. Use isInitial()");
	}

	hasRuntime() {
		if(this.entrypoints.length === 0) return false;
		return this.entrypoints[0].chunks[0] === this;
	}

	isInitial() {
		return this.entrypoints.length > 0;
	}

	hasEntryModule() {
		return !!this.entryModule;
	}

	addToCollection(collection, item) {
		if(item === this) {
			return false;
		}

		if(collection.indexOf(item) > -1) {
			return false;
		}

		collection.push(item);
		return true;
	}

	addChunk(chunk) {
		return this.addToCollection(this.chunks, chunk);
	}

	addParent(parentChunk) {
		return this.addToCollection(this.parents, parentChunk);
	}

	addModule(module) {
		return this.addToCollection(this.modules, module);
	}

	addBlock(block) {
		return this.addToCollection(this.blocks, block);
	}

	removeModule(module) {
		removeAndDo(this.modules, module, "removeChunk", this);
	}

	removeChunk(chunk) {
		removeAndDo(this.chunks, chunk, "removeParent", this);
	}

	removeParent(chunk) {
		removeAndDo(this.parents, chunk, "removeChunk", this);
	}

	addOrigin(module, loc) {
		this.origins.push({
			module,
			loc,
			name: this.name
		});
	}

	remove(reason) {
		// cleanup modules
		this.modules.slice().forEach(module => {
			module.removeChunk(this);
		});

		// cleanup parents
		this.parents.forEach(parentChunk => {
			// remove this chunk from its parents
			const idx = parentChunk.chunks.indexOf(this);
			if(idx >= 0) {
				parentChunk.chunks.splice(idx, 1);
			}

			// cleanup "sub chunks"
			this.chunks.forEach(chunk => {
				/**
				 * remove this chunk as "intermediary" and connect
				 * it "sub chunks" and parents directly
				 */
				// add parent to each "sub chunk"
				chunk.addParent(parentChunk);
				// add "sub chunk" to parent
				parentChunk.addChunk(chunk);

				// remove this as parent of every "sub chunk"
				const idx = chunk.parents.indexOf(this);
				if(idx >= 0) {
					chunk.parents.splice(idx, 1);
				}
			});
		});

		// cleanup blocks
		this.blocks.forEach(block => {
			const idx = block.chunks.indexOf(this);
			if(idx >= 0) {
				block.chunks.splice(idx, 1);
				if(block.chunks.length === 0) {
					block.chunks = null;
					block.chunkReason = reason;
				}
			}
		});
	}

	moveModule(module, otherChunk) {
		module.removeChunk(this);
		module.addChunk(otherChunk);
		otherChunk.addModule(module);
		module.rewriteChunkInReasons(this, [otherChunk]);
	}

	replaceChunk(oldChunk, newChunk) {
		const idx = this.chunks.indexOf(oldChunk);
		if(idx >= 0) {
			this.chunks.splice(idx, 1);
		}
		if(this !== newChunk && newChunk.addParent(this)) {
			this.addChunk(newChunk);
		}
	}

	replaceParentChunk(oldParentChunk, newParentChunk) {
		const idx = this.parents.indexOf(oldParentChunk);
		if(idx >= 0) {
			this.parents.splice(idx, 1);
		}
		if(this !== newParentChunk && newParentChunk.addChunk(this)) {
			this.addParent(newParentChunk);
		}
	}

	integrate(otherChunk, reason) {
		if(!this.canBeIntegrated(otherChunk)) {
			return false;
		}

		const otherChunkModules = otherChunk.modules.slice();
		otherChunkModules.forEach(module => otherChunk.moveModule(module, this));
		otherChunk.modules.length = 0;

		otherChunk.parents.forEach(parentChunk => parentChunk.replaceChunk(otherChunk, this));
		otherChunk.parents.length = 0;

		otherChunk.chunks.forEach(chunk => chunk.replaceParentChunk(otherChunk, this));
		otherChunk.chunks.length = 0;

		otherChunk.blocks.forEach(b => {
			b.chunks = b.chunks ? b.chunks.map(c => {
				return c === otherChunk ? this : c;
			}) : [this];
			b.chunkReason = reason;
			this.addBlock(b);
		});
		otherChunk.blocks.length = 0;

		otherChunk.origins.forEach(origin => {
			this.origins.push(origin);
		});
		this.origins.forEach(origin => {
			if(!origin.reasons) {
				origin.reasons = [reason];
			} else if(origin.reasons[0] !== reason) {
				origin.reasons.unshift(reason);
			}
		});
		this.chunks = this.chunks.filter(chunk => {
			return chunk !== otherChunk && chunk !== this;
		});
		this.parents = this.parents.filter(parentChunk => {
			return parentChunk !== otherChunk && parentChunk !== this;
		});
		return true;
	}

	split(newChunk) {
		this.blocks.forEach(block => {
			newChunk.blocks.push(block);
			block.chunks.push(newChunk);
		});
		this.chunks.forEach(chunk => {
			newChunk.chunks.push(chunk);
			chunk.parents.push(newChunk);
		});
		this.parents.forEach(parentChunk => {
			parentChunk.chunks.push(newChunk);
			newChunk.parents.push(parentChunk);
		});
		this.entrypoints.forEach(entrypoint => {
			entrypoint.insertChunk(newChunk, this);
		});
	}

	isEmpty() {
		return this.modules.length === 0;
	}

	updateHash(hash) {
		hash.update(`${this.id} `);
		hash.update(this.ids ? this.ids.join(",") : "");
		hash.update(`${this.name || ""} `);
		this.modules.forEach(m => m.updateHash(hash));
	}

	canBeIntegrated(otherChunk) {
		if(otherChunk.isInitial()) {
			return false;
		}
		if(this.isInitial()) {
			if(otherChunk.parents.length !== 1 || otherChunk.parents[0] !== this) {
				return false;
			}
		}
		return true;
	}

	addMultiplierAndOverhead(size, options) {
		const overhead = typeof options.chunkOverhead === "number" ? options.chunkOverhead : 10000;
		const multiplicator = this.isInitial() ? (options.entryChunkMultiplicator || 10) : 1;

		return size * multiplicator + overhead;
	}

	modulesSize() {
		let count = 0;
		for(let i = 0; i < this.modules.length; i++) {
			count += this.modules[i].size();
		}
		return count;
	}

	size(options) {
		return this.addMultiplierAndOverhead(this.modulesSize(), options);
	}

	integratedSize(otherChunk, options) {
		// Chunk if it's possible to integrate this chunk
		if(!this.canBeIntegrated(otherChunk)) {
			return false;
		}

		const modulesSize = this.modulesSize() + otherChunk.modulesSize();
		return this.addMultiplierAndOverhead(modulesSize, options);
	}

	getChunkMaps(includeEntries, realHash) {
		const chunksProcessed = [];
		const chunkHashMap = {};
		const chunkNameMap = {};
		(function addChunk(chunk) {
			if(chunksProcessed.indexOf(chunk) >= 0) return;
			chunksProcessed.push(chunk);
			if(!chunk.hasRuntime() || includeEntries) {
				chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
				if(chunk.name)
					chunkNameMap[chunk.id] = chunk.name;
			}
			chunk.chunks.forEach(addChunk);
		}(this));
		return {
			hash: chunkHashMap,
			name: chunkNameMap
		};
	}

	sortItems() {
		this.modules.sort(byId);
		this.origins.sort((a, b) => {
			const aIdent = a.module.identifier();
			const bIdent = b.module.identifier();
			if(aIdent < bIdent) return -1;
			if(aIdent > bIdent) return 1;
			return compareLocations(a.loc, b.loc);
		});
		this.origins.forEach(origin => {
			if(origin.reasons)
				origin.reasons.sort();
		});
		this.parents.sort(byId);
		this.chunks.sort(byId);
	}

	toString() {
		return `Chunk[${this.modules.join()}]`;
	}

	checkConstraints() {
		const chunk = this;
		chunk.chunks.forEach((child, idx) => {
			if(chunk.chunks.indexOf(child) !== idx)
				throw new Error(`checkConstraints: duplicate child in chunk ${chunk.debugId} ${child.debugId}`);
			if(child.parents.indexOf(chunk) < 0)
				throw new Error(`checkConstraints: child missing parent ${chunk.debugId} -> ${child.debugId}`);
		});
		chunk.parents.forEach((parentChunk, idx) => {
			if(chunk.parents.indexOf(parentChunk) !== idx)
				throw new Error(`checkConstraints: duplicate parent in chunk ${chunk.debugId} ${parentChunk.debugId}`);
			if(parentChunk.chunks.indexOf(chunk) < 0)
				throw new Error(`checkConstraints: parent missing child ${parentChunk.debugId} <- ${chunk.debugId}`);
		});
	}
}

module.exports = Chunk;
<|endoftext|>


<commit_before>// Carga la division estatal de un inicio
var cargaDivisionEstatal = function()
{
    var svg = d3.select(map.getPanes().overlayPane).append('svg').attr('id', 'svg-division-estatal');
    var g = svg.append('g').attr('class', 'leaflet-zoom-hide');

    d3.json('/topojson/estado.json', function (error, collection) {
        var bounds = d3.geo.bounds(topojson.feature(collection, collection.objects['collection']));
        var path = d3.geo.path().projection(projectPoint);

        var feature = g.selectAll('.region')
            .data(topojson.feature(collection, collection.objects['collection']).features)
            .enter()
            .append('path')
            .attr('class', 'region leaflet-clickable')
            .on('mouseover', function(d){
                nombreRegion(opciones.datos[d.properties.region_id].properties);
            })
            .on('dblclick', function(d){
                seleccionaEstado(d.properties.region_id);
            })
            .each(function(d){
                // Asigna los valores la primera y unica vez que carga los estados
                opciones.datos[d.properties.region_id] = {};
                opciones.datos[d.properties.region_id].properties = d.properties;
                opciones.datos[d.properties.region_id].properties.layer = $(this);
                opciones.datos[d.properties.region_id].properties.tipo_region = 'estado';
                opciones.datos[d.properties.region_id].properties.bounds = d3.geo.bounds(d);
                completaSelect(opciones.datos[d.properties.region_id].properties);
            });

        map.on('zoomend', reinicia);
        map.on('zoomstart', function(){muestraOcultaSvg();});
        reinicia(); // Lo inicializa

        // Reposiciona el svg si se realiza un zoom
        function reinicia()
        {
            var bottomLeft = projectPoint(bounds[0]);
            var topRight = projectPoint(bounds[1]);

            svg.attr('width', topRight[0] - bottomLeft[0])
                .attr('height', bottomLeft[1] - topRight[1])
                .style('margin-left', bottomLeft[0] + 'px')
                .style('margin-top', topRight[1] + 'px');

            g.attr('transform', 'translate(' + -bottomLeft[0] + ',' + -topRight[1] + ')');
            feature.attr('d', path);
            muestraOcultaSvg(true);
        }
    });
};

// Carga todos los municipios de cierto estado
var cargaDivisionMunicipal = function()
{
    var svg = d3.select(map.getPanes().overlayPane).append('svg').attr('id', 'svg-division-municipal');
    var g = svg.append('g').attr('class', 'leaflet-zoom-hide');

    d3.json('/topojson/estado_' + opciones.estado_seleccionado + '_division_municipal.json', function (error, collection) {
        var bounds = d3.geo.bounds(topojson.feature(collection, collection.objects['collection']));
        var path = d3.geo.path().projection(projectPoint);

        var feature = g.selectAll('.region')
            .data(topojson.feature(collection, collection.objects['collection']).features)
            .enter()
            .append('path')
            .attr('class', 'region leaflet-clickable')
            .on('mouseover', function(d){
                nombreRegion(opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties);
            })
            .on('dblclick', function(d){
                seleccionaMunicipio(d.properties.region_id);
            })
            .each(function(d){
                if (opciones.datos[opciones.estado_seleccionado].municipios == undefined)
                    opciones.datos[opciones.estado_seleccionado].municipios = [];

                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)] = {};
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties = d.properties;
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties.layer = $(this);
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties.tipo_region = 'municipio';
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties.bounds = d3.geo.bounds(d);

                completaSelect(opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties);
            });

        map.on('zoomend', reinicia);
        map.on('zoomstart', function(){muestraOcultaSvg();});

        // Reposition the SVG to cover the features.
        function reinicia()
        {
            var bottomLeft = projectPoint(bounds[0]);
            var topRight = projectPoint(bounds[1]);

            svg.attr('width', topRight[0] - bottomLeft[0])
                .attr('height', bottomLeft[1] - topRight[1])
                .style('margin-left', bottomLeft[0] + 'px')
                .style('margin-top', topRight[1] + 'px');

            g.attr('transform', 'translate(' + -bottomLeft[0] + ',' + -topRight[1] + ')');
            feature.attr('d', path);
            muestraOcultaSvg(true);
        }
    });
};

// Carga una sola region en especifico, municipios o estado; reutilizando esta funcion
// se necesita un obj con prop: region_id, centroide, tipo_region, parent_id
var cargaRegion = function(prop)
{
    switch(prop.tipo_region)
    {
        case 'estado':
            cargaDivisionMunicipal();
            break;
        case 'municipio':
            break;
    }

    map.flyToBounds([prop.bounds[0].reverse(), prop.bounds[1].reverse()]);
    cargaGrupos();
    borraRegistrosAnteriores();
    $('#svg-division-estatal .selecciona-region').attr('class', 'region');
    $('#svg-division-municipal .selecciona-region').attr('class', 'region');
    prop.layer.attr('class', 'selecciona-region');
};

// Para cuando se hace una animacion con flyTo, los svgs de estatal o municipal o region sola
var muestraOcultaSvg = function(caso)
{
    if (caso)
    {
        $('#svg-division-estatal').css('visibility', 'visible');
        $('#svg-division-municipal').css('visibility', 'visible');
        $('#svg-region').css('visibility', 'visible');
    } else {
        $('#svg-division-estatal').css('visibility', 'hidden');
        $('#svg-division-municipal').css('visibility', 'hidden');
        $('#svg-region').css('visibility', 'hidden');
    }
};

// Hace la projecccion de los puntos en D3 cuando se hace un zoom
var projectPoint = function(x)
{
    var point = map.latLngToLayerPoint(new L.LatLng(x[1], x[0]));
    return [point.x, point.y];
};

/** FUNCIONES CORE **/

function addTopoData(opts){
    if (opts.clean) opts.layer.clearLayers();

    opts.layer.addData(opts.topojson);
    opts.layer.addTo(map);

    if (opts.fillColor == undefined) opts.fillColor = '#d8d8d8';
    if (opts.color == undefined) opts.color = '#808080';
    handleLayer(opts);
}

function handleLayer(opts){
    opts.layer.setStyle({
        fillColor : opts.fillColor,
        fillOpacity:.5,
        color: opts.color,
        weight:1,
        opacity:.5
    });
    opts.layer.on({
        mouseover : enterLayer,
        mouseout: leaveLayer
    });
}

function enterLayer(){
    this.bringToFront();
    this.setStyle({
        weight:3,
        opacity:.5
    });
}

function leaveLayer(){
    this.bringToBack();
    this.setStyle({
        weight:1,
        opacity:.5
    });
}<commit_msg>bug cuanod se seleccionaba una region dos veces<commit_after>// Carga la division estatal de un inicio
var cargaDivisionEstatal = function()
{
    var svg = d3.select(map.getPanes().overlayPane).append('svg').attr('id', 'svg-division-estatal');
    var g = svg.append('g').attr('class', 'leaflet-zoom-hide');

    d3.json('/topojson/estado.json', function (error, collection) {
        var bounds = d3.geo.bounds(topojson.feature(collection, collection.objects['collection']));
        var path = d3.geo.path().projection(projectPoint);

        var feature = g.selectAll('.region')
            .data(topojson.feature(collection, collection.objects['collection']).features)
            .enter()
            .append('path')
            .attr('class', 'region leaflet-clickable')
            .on('mouseover', function(d){
                nombreRegion(opciones.datos[d.properties.region_id].properties);
            })
            .on('dblclick', function(d){
                seleccionaEstado(d.properties.region_id);
            })
            .each(function(d){
                // Asigna los valores la primera y unica vez que carga los estados
                opciones.datos[d.properties.region_id] = {};
                opciones.datos[d.properties.region_id].properties = d.properties;
                opciones.datos[d.properties.region_id].properties.layer = $(this);
                opciones.datos[d.properties.region_id].properties.tipo_region = 'estado';

                var bounds = d3.geo.bounds(d)
                opciones.datos[d.properties.region_id].properties.bounds = [bounds[0].reverse(), bounds[1].reverse()];

                completaSelect(opciones.datos[d.properties.region_id].properties);
            });

        map.on('zoomend', reinicia);
        map.on('zoomstart', function(){muestraOcultaSvg();});
        reinicia(); // Lo inicializa

        // Reposiciona el svg si se realiza un zoom
        function reinicia()
        {
            var bottomLeft = projectPoint(bounds[0]);
            var topRight = projectPoint(bounds[1]);

            svg.attr('width', topRight[0] - bottomLeft[0])
                .attr('height', bottomLeft[1] - topRight[1])
                .style('margin-left', bottomLeft[0] + 'px')
                .style('margin-top', topRight[1] + 'px');

            g.attr('transform', 'translate(' + -bottomLeft[0] + ',' + -topRight[1] + ')');
            feature.attr('d', path);
            muestraOcultaSvg(true);
        }
    });
};

// Carga todos los municipios de cierto estado
var cargaDivisionMunicipal = function()
{
    var svg = d3.select(map.getPanes().overlayPane).append('svg').attr('id', 'svg-division-municipal');
    var g = svg.append('g').attr('class', 'leaflet-zoom-hide');

    d3.json('/topojson/estado_' + opciones.estado_seleccionado + '_division_municipal.json', function (error, collection) {
        var bounds = d3.geo.bounds(topojson.feature(collection, collection.objects['collection']));
        var path = d3.geo.path().projection(projectPoint);

        var feature = g.selectAll('.region')
            .data(topojson.feature(collection, collection.objects['collection']).features)
            .enter()
            .append('path')
            .attr('class', 'region leaflet-clickable')
            .on('mouseover', function(d){
                nombreRegion(opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties);
            })
            .on('dblclick', function(d){
                seleccionaMunicipio(d.properties.region_id);
            })
            .each(function(d){
                if (opciones.datos[opciones.estado_seleccionado].municipios == undefined)
                    opciones.datos[opciones.estado_seleccionado].municipios = [];

                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)] = {};
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties = d.properties;
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties.layer = $(this);
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties.tipo_region = 'municipio';

                var bounds = d3.geo.bounds(d)
                opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties.bounds = [bounds[0].reverse(), bounds[1].reverse()];

                completaSelect(opciones.datos[opciones.estado_seleccionado].municipios[parseInt(d.properties.region_id)].properties);
            });

        map.on('zoomend', reinicia);
        map.on('zoomstart', function(){muestraOcultaSvg();});

        // Reposition the SVG to cover the features.
        function reinicia()
        {
            var bottomLeft = projectPoint(bounds[0]);
            var topRight = projectPoint(bounds[1]);

            svg.attr('width', topRight[0] - bottomLeft[0])
                .attr('height', bottomLeft[1] - topRight[1])
                .style('margin-left', bottomLeft[0] + 'px')
                .style('margin-top', topRight[1] + 'px');

            g.attr('transform', 'translate(' + -bottomLeft[0] + ',' + -topRight[1] + ')');
            feature.attr('d', path);
            muestraOcultaSvg(true);
        }
    });
};

// Carga una sola region en especifico, municipios o estado; reutilizando esta funcion
// se necesita un obj con prop: region_id, centroide, tipo_region, parent_id
var cargaRegion = function(prop)
{
    switch(prop.tipo_region)
    {
        case 'estado':
            cargaDivisionMunicipal();
            break;
        case 'municipio':
            break;
    }

    map.flyToBounds(prop.bounds);
    cargaGrupos();
    borraRegistrosAnteriores();
    $('#svg-division-estatal .selecciona-region').attr('class', 'region');
    $('#svg-division-municipal .selecciona-region').attr('class', 'region');
    prop.layer.attr('class', 'selecciona-region');
};

// Para cuando se hace una animacion con flyTo, los svgs de estatal o municipal o region sola
var muestraOcultaSvg = function(caso)
{
    if (caso)
    {
        $('#svg-division-estatal').css('visibility', 'visible');
        $('#svg-division-municipal').css('visibility', 'visible');
        $('#svg-region').css('visibility', 'visible');
    } else {
        $('#svg-division-estatal').css('visibility', 'hidden');
        $('#svg-division-municipal').css('visibility', 'hidden');
        $('#svg-region').css('visibility', 'hidden');
    }
};

// Hace la projecccion de los puntos en D3 cuando se hace un zoom
var projectPoint = function(x)
{
    var point = map.latLngToLayerPoint(new L.LatLng(x[1], x[0]));
    return [point.x, point.y];
};

/** FUNCIONES CORE **/

function addTopoData(opts){
    if (opts.clean) opts.layer.clearLayers();

    opts.layer.addData(opts.topojson);
    opts.layer.addTo(map);

    if (opts.fillColor == undefined) opts.fillColor = '#d8d8d8';
    if (opts.color == undefined) opts.color = '#808080';
    handleLayer(opts);
}

function handleLayer(opts){
    opts.layer.setStyle({
        fillColor : opts.fillColor,
        fillOpacity:.5,
        color: opts.color,
        weight:1,
        opacity:.5
    });
    opts.layer.on({
        mouseover : enterLayer,
        mouseout: leaveLayer
    });
}

function enterLayer(){
    this.bringToFront();
    this.setStyle({
        weight:3,
        opacity:.5
    });
}

function leaveLayer(){
    this.bringToBack();
    this.setStyle({
        weight:1,
        opacity:.5
    });
}<|endoftext|>