<commit_before>'use strict';

/*jshint expr: true*/

const expect = require( 'chai' ).expect;

const sinon = require( 'sinon' );

const LambdaRunner = require( '../../lib/runner' );

describe( 'lib/runner', function() {

    describe( 'LambdaRunner', function() {

        describe( 'constructor', function() {

            it( 'using defaults', function() {

                let instance = new LambdaRunner( 'callback:result' );

                expect( instance.method ).to.equal( 'callback' );
                expect( instance.expectedOutcome ).to.equal( 'result' );
                expect( instance.verifier ).to.be.undefined;
                expect( instance.timeout ).to.equal( 3000 );
                expect( instance.enforceTimeout ).to.be.false;
                expect( instance.wantLeakDetection ).to.be.false;
            });

            it( 'using specified options and verifier', function() {

                let verifier = function() {};

                let instance = new LambdaRunner( 'callback:result', verifier, { timeout: 10000, checkForHandleLeak: true } );

                expect( instance.method ).to.equal( 'callback' );
                expect( instance.expectedOutcome ).to.equal( 'result' );
                expect( instance.verifier ).to.equal( verifier );
                expect( instance.timeout ).to.equal( 10000 );
                expect( instance.enforceTimeout ).to.be.true;
                expect( instance.wantLeakDetection ).to.be.true;
            });
        });

        describe( '.withEvent', function() {

            it( 'normal operation', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} );

                expect( instance.event ).to.not.exist;

                let returnValue = instance.withEvent( {} );

                expect( returnValue ).to.equal( instance );
                expect( instance.event ).to.exist;
                expect( instance.event ).to.eql( {} );
            });
        });

        describe( '.withContext', function() {

            it( 'normal operation', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} );

                expect( instance.context ).to.not.exist;

                let returnValue = instance.withContext( { whatever: 1234} );

                expect( returnValue ).to.equal( instance );

                expect( instance.context ).to.exist;
                expect( instance.context.functionName ).to.exist;
                expect( instance.context.functionVersion ).to.exist;
                expect( instance.context.memoryLimitInMB ).to.exist;
                expect( instance.context.logGroupName ).to.exist;
                expect( instance.context.logStreamName ).to.exist;
                expect( instance.context.invokedFunctionArn ).to.exist;
                expect( instance.context.invokeid ).to.exist;
                expect( instance.context.awsRequestId ).to.exist;

                expect( instance.context.whatever ).to.equal( 1234 );
            });
        });

        describe( '.run', function() {

            it( 'successful callback:result using defaults', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} ).withEvent( {} );

                return instance.run( (event, context, callback) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        callback( null, 'ok' );
                    });
            });

            it( 'successful callback:result with event, context, and verifier', function() {

                let verifier = sinon.stub();

                let instance = new LambdaRunner( 'callback:result', verifier, { checkForHandleLeak: true } )
                    .withEvent( { answer: 42 } )
                    .withContext( { whatever: true } );

                return instance.run( (event, context, callback) => {

                        expect( event ).to.eql( { answer: 42 } );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;
                        expect( context.whatever ).to.be.true;

                        callback( null, 'ok' );
                    })
                    .then( () => {

                        expect( verifier.calledOnce ).to.be.true;
                        expect( verifier.firstCall.args[0] ).to.equal( 'ok' );
                        expect( verifier.firstCall.args[1].execTime ).to.exist;
                    });
            });

            it( 'successful callback:error using defaults', function() {

                let instance = new LambdaRunner( 'callback:error', null, {} ).withEvent( {} );

                return instance.run( (event, context, callback) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        callback( new Error( 'bang' ) );
                    });
            });

            it( 'successful Promise.resolve using defaults', function() {

                let instance = new LambdaRunner( 'Promise.resolve', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        return Promise.resolve( 'ok' );
                    });
            });

            it( 'successful Promise.resolve with event, context, and verifier', function() {

                let verifier = sinon.stub();

                let instance = new LambdaRunner( 'Promise.resolve', verifier, { checkForHandleLeak: true } )
                    .withEvent( { answer: 42 } )
                    .withContext( { whatever: true } );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( { answer: 42 } );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;
                        expect( context.whatever ).to.be.true;

                        return Promise.resolve( 'ok' );
                    })
                    .then( () => {

                        expect( verifier.calledOnce ).to.be.true;
                        expect( verifier.firstCall.args[0] ).to.equal( 'ok' );
                        expect( verifier.firstCall.args[1].execTime ).to.exist;
                    });
            });

            it( 'successful Promise.reject using defaults', function() {

                let instance = new LambdaRunner( 'Promise.reject', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        return Promise.reject( new Error( 'bang' ) );
                    });
            });

            it( 'successful context.succeed using defaults', function() {

                let instance = new LambdaRunner( 'context.succeed', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.succeed( 'ok' );
                    });
            });

            it( 'successful context.succeed using defaults, handler calling done', function() {

                let instance = new LambdaRunner( 'context.succeed', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.done( null, 'ok' );
                    });
            });

            it( 'unexpected failure when context.succeed expected', function() {

                let instance = new LambdaRunner( 'context.succeed', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {
                        return new Promise(() => {
                            throw new Error('Bang');
                        });
                    }).then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {
                            expect( err.stack ).to.contain( 'runner.test.js' );
                        }
                    );
            });

            it( 'successful context.fail using defaults', function() {

                let instance = new LambdaRunner( 'context.fail', null, { timeout: 3000 } ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 2900 );

                        context.fail( new Error( 'bang' ) );
                    });
            });

            it( 'successful context.fail using defaults, handler calling done', function() {

                let instance = new LambdaRunner( 'context.fail', null, { timeout: 3000 } ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 2900 );

                        context.done( new Error( 'bang' ) );
                    });
            });

            it( 'successful context.fail using defaults, error is a string', function() {

                let verifier = sinon.stub();

                let instance = new LambdaRunner( 'context.fail', verifier, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.fail( 'bang' );
                    })
                    .then( () => {

                        expect( verifier.calledOnce ).to.be.true;
                        expect( verifier.firstCall.args[0].constructor.name ).to.equal( 'Error' );
                        expect( verifier.firstCall.args[0].message ).to.equal( 'bang' );
                    });
            });

            it( 'fail when context.fail requested and context.succeed called', function() {

                let instance = new LambdaRunner( 'context.fail', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.succeed( 'ok' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'context.succeed() called instead of context.fail()' );
                        }
                    );
            });

            it( 'fail when timeout occurs', function() {

                let instance = new LambdaRunner( 'context.fail', null, { timeout: 1000 } );

                return instance.run( (event, context, callback) => {

                        setTimeout( () => {

                            callback( null, 'ok' );

                        }, 1010 );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.contain( 'timeout after: 1000ms' );
                        }
                    );
            });

            it( 'fail when callback used, expects a result but error is passed', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} );

                return instance.run( (event, context, callback) => {

                        callback( new Error( 'bang' ) );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            // Issue #19
                            expect( err.message ).to.equal( 'bang' );
                        }
                    );
            });

            it( 'fail when callback used, expects an error but result is passed', function() {

                let instance = new LambdaRunner( 'callback:error', null, {} );

                return instance.run( (event, context, callback) => {

                        callback( null, 'ok' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'expecting error but got result' );
                        }
                    );
            });

            it( 'fail when error thrown during execution', function() {

                let instance = new LambdaRunner( 'callback:error', null, {} );

                return instance.run( (event, context, callback) => {

                        throw new Error( 'bang' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'bang' );
                        }
                    );
            });

            it( 'fail when leak is detected', function() {

                let instance = new LambdaRunner( 'callback:result', null, { checkForHandleLeak: true } );

                return instance.run( (event, context, callback) => {

                        setTimeout( ()=>{}, 100 );

                        callback( null, 'ok' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'Potential handle leakage detected' );
                        }
                    );
            });
        });
    });
});
<commit_msg>Imporove test description<commit_after>'use strict';

/*jshint expr: true*/

const expect = require( 'chai' ).expect;

const sinon = require( 'sinon' );

const LambdaRunner = require( '../../lib/runner' );

describe( 'lib/runner', function() {

    describe( 'LambdaRunner', function() {

        describe( 'constructor', function() {

            it( 'using defaults', function() {

                let instance = new LambdaRunner( 'callback:result' );

                expect( instance.method ).to.equal( 'callback' );
                expect( instance.expectedOutcome ).to.equal( 'result' );
                expect( instance.verifier ).to.be.undefined;
                expect( instance.timeout ).to.equal( 3000 );
                expect( instance.enforceTimeout ).to.be.false;
                expect( instance.wantLeakDetection ).to.be.false;
            });

            it( 'using specified options and verifier', function() {

                let verifier = function() {};

                let instance = new LambdaRunner( 'callback:result', verifier, { timeout: 10000, checkForHandleLeak: true } );

                expect( instance.method ).to.equal( 'callback' );
                expect( instance.expectedOutcome ).to.equal( 'result' );
                expect( instance.verifier ).to.equal( verifier );
                expect( instance.timeout ).to.equal( 10000 );
                expect( instance.enforceTimeout ).to.be.true;
                expect( instance.wantLeakDetection ).to.be.true;
            });
        });

        describe( '.withEvent', function() {

            it( 'normal operation', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} );

                expect( instance.event ).to.not.exist;

                let returnValue = instance.withEvent( {} );

                expect( returnValue ).to.equal( instance );
                expect( instance.event ).to.exist;
                expect( instance.event ).to.eql( {} );
            });
        });

        describe( '.withContext', function() {

            it( 'normal operation', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} );

                expect( instance.context ).to.not.exist;

                let returnValue = instance.withContext( { whatever: 1234} );

                expect( returnValue ).to.equal( instance );

                expect( instance.context ).to.exist;
                expect( instance.context.functionName ).to.exist;
                expect( instance.context.functionVersion ).to.exist;
                expect( instance.context.memoryLimitInMB ).to.exist;
                expect( instance.context.logGroupName ).to.exist;
                expect( instance.context.logStreamName ).to.exist;
                expect( instance.context.invokedFunctionArn ).to.exist;
                expect( instance.context.invokeid ).to.exist;
                expect( instance.context.awsRequestId ).to.exist;

                expect( instance.context.whatever ).to.equal( 1234 );
            });
        });

        describe( '.run', function() {

            it( 'successful callback:result using defaults', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} ).withEvent( {} );

                return instance.run( (event, context, callback) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        callback( null, 'ok' );
                    });
            });

            it( 'successful callback:result with event, context, and verifier', function() {

                let verifier = sinon.stub();

                let instance = new LambdaRunner( 'callback:result', verifier, { checkForHandleLeak: true } )
                    .withEvent( { answer: 42 } )
                    .withContext( { whatever: true } );

                return instance.run( (event, context, callback) => {

                        expect( event ).to.eql( { answer: 42 } );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;
                        expect( context.whatever ).to.be.true;

                        callback( null, 'ok' );
                    })
                    .then( () => {

                        expect( verifier.calledOnce ).to.be.true;
                        expect( verifier.firstCall.args[0] ).to.equal( 'ok' );
                        expect( verifier.firstCall.args[1].execTime ).to.exist;
                    });
            });

            it( 'successful callback:error using defaults', function() {

                let instance = new LambdaRunner( 'callback:error', null, {} ).withEvent( {} );

                return instance.run( (event, context, callback) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        callback( new Error( 'bang' ) );
                    });
            });

            it( 'successful Promise.resolve using defaults', function() {

                let instance = new LambdaRunner( 'Promise.resolve', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        return Promise.resolve( 'ok' );
                    });
            });

            it( 'successful Promise.resolve with event, context, and verifier', function() {

                let verifier = sinon.stub();

                let instance = new LambdaRunner( 'Promise.resolve', verifier, { checkForHandleLeak: true } )
                    .withEvent( { answer: 42 } )
                    .withContext( { whatever: true } );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( { answer: 42 } );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;
                        expect( context.whatever ).to.be.true;

                        return Promise.resolve( 'ok' );
                    })
                    .then( () => {

                        expect( verifier.calledOnce ).to.be.true;
                        expect( verifier.firstCall.args[0] ).to.equal( 'ok' );
                        expect( verifier.firstCall.args[1].execTime ).to.exist;
                    });
            });

            it( 'successful Promise.reject using defaults', function() {

                let instance = new LambdaRunner( 'Promise.reject', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        return Promise.reject( new Error( 'bang' ) );
                    });
            });

            it( 'successful context.succeed using defaults', function() {

                let instance = new LambdaRunner( 'context.succeed', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.succeed( 'ok' );
                    });
            });

            it( 'successful context.succeed using defaults, handler calling done', function() {

                let instance = new LambdaRunner( 'context.succeed', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.done( null, 'ok' );
                    });
            });

            it( 'maintains the stack when there is an unexpected failure and context.succeed expected', function() {

                let instance = new LambdaRunner( 'context.succeed', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {
                        return new Promise(() => {
                            throw new Error('Bang');
                        });
                    }).then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {
                            expect( err.stack ).to.contain( 'runner.test.js' );
                        }
                    );
            });

            it( 'successful context.fail using defaults', function() {

                let instance = new LambdaRunner( 'context.fail', null, { timeout: 3000 } ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 2900 );

                        context.fail( new Error( 'bang' ) );
                    });
            });

            it( 'successful context.fail using defaults, handler calling done', function() {

                let instance = new LambdaRunner( 'context.fail', null, { timeout: 3000 } ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 2900 );

                        context.done( new Error( 'bang' ) );
                    });
            });

            it( 'successful context.fail using defaults, error is a string', function() {

                let verifier = sinon.stub();

                let instance = new LambdaRunner( 'context.fail', verifier, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.fail( 'bang' );
                    })
                    .then( () => {

                        expect( verifier.calledOnce ).to.be.true;
                        expect( verifier.firstCall.args[0].constructor.name ).to.equal( 'Error' );
                        expect( verifier.firstCall.args[0].message ).to.equal( 'bang' );
                    });
            });

            it( 'fail when context.fail requested and context.succeed called', function() {

                let instance = new LambdaRunner( 'context.fail', null, {} ).withEvent( {} );

                return instance.run( (event, context) => {

                        expect( event ).to.eql( {} );
                        expect( context ).to.exist;
                        expect( context.functionName ).to.exist;

                        expect( context.getRemainingTimeInMillis() ).to.be.above( 0 );

                        context.succeed( 'ok' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'context.succeed() called instead of context.fail()' );
                        }
                    );
            });

            it( 'fail when timeout occurs', function() {

                let instance = new LambdaRunner( 'context.fail', null, { timeout: 1000 } );

                return instance.run( (event, context, callback) => {

                        setTimeout( () => {

                            callback( null, 'ok' );

                        }, 1010 );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.contain( 'timeout after: 1000ms' );
                        }
                    );
            });

            it( 'fail when callback used, expects a result but error is passed', function() {

                let instance = new LambdaRunner( 'callback:result', null, {} );

                return instance.run( (event, context, callback) => {

                        callback( new Error( 'bang' ) );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            // Issue #19
                            expect( err.message ).to.equal( 'bang' );
                        }
                    );
            });

            it( 'fail when callback used, expects an error but result is passed', function() {

                let instance = new LambdaRunner( 'callback:error', null, {} );

                return instance.run( (event, context, callback) => {

                        callback( null, 'ok' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'expecting error but got result' );
                        }
                    );
            });

            it( 'fail when error thrown during execution', function() {

                let instance = new LambdaRunner( 'callback:error', null, {} );

                return instance.run( (event, context, callback) => {

                        throw new Error( 'bang' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'bang' );
                        }
                    );
            });

            it( 'fail when leak is detected', function() {

                let instance = new LambdaRunner( 'callback:result', null, { checkForHandleLeak: true } );

                return instance.run( (event, context, callback) => {

                        setTimeout( ()=>{}, 100 );

                        callback( null, 'ok' );
                    })
                    .then(
                        () => {

                            throw new Error( 'should not resolve' );
                        },
                        (err) => {

                            expect( err.message ).to.equal( 'Potential handle leakage detected' );
                        }
                    );
            });
        });
    });
});
<|endoftext|>


<commit_before>var Promise = require('bluebird');
var Collection = require('./collection');

var Store = function(idb) {
	this._idb = idb;
	this._collections = {};
}

Store.prototype.open = function(callback) {
	var self = this;
	var request = this._idb.open('vdb', 1);
	return new Promise(function(resolve, reject) {
		request.onsuccess = function(event) {
			self._db = event.target.result;
			resolve(self);
		}
		request.onupgradeneeded = function(event) {
			var db = event.target.result;
			if(event.oldVersion < 1) {
				var documents = db.createObjectStore('documents', {keyPath: '$collectionKey', unique: true});
				documents.createIndex('$collection', '$collection', {unique:false});
			}
			//fix for _id being keypath...
			if(event.oldVersion < 2) {
				db.deleteObjectStore('documents');
				var documents = db.createObjectStore('documents', {keyPath: '$collectionKey', unique: true});
				documents.createIndex('$collection', '$collection', {unique:false});
			}
			//self._documents.createIndex('streamIdCommitSequence', 'streamIdCommitSequence', {unique:true});
			//db.createObjectStore('commits', {keyPath: 'id'});
		}
		request.onerror = function(event) {
			reject(new Error(event));
		}
		request.onblocked = function(event) {
			reject(new Error(event));
		}
	}).nodeify(callback);
}

Store.prototype.collection = function(name, callback) {
	var collection = this._collections[name];
	if(!collection) {
		collection = this._collections[name] = new Collection(this._db, name);
	}
	if(callback) {
		callback(collection);
	}
	return collection;
}

module.exports = Store;<commit_msg>changed back second argument in _idb.open to 2 from 1 since the browser would crash otherwise<commit_after>var Promise = require('bluebird');
var Collection = require('./collection');

var Store = function(idb) {
	this._idb = idb;
	this._collections = {};
}

Store.prototype.open = function(callback) {
	var self = this;
	var request = this._idb.open('vdb', 2);
	return new Promise(function(resolve, reject) {
		request.onsuccess = function(event) {
			self._db = event.target.result;
			resolve(self);
		}
		request.onupgradeneeded = function(event) {
			var db = event.target.result;
			if(event.oldVersion < 1) {
				var documents = db.createObjectStore('documents', {keyPath: '$collectionKey', unique: true});
				documents.createIndex('$collection', '$collection', {unique:false});
			}
			//fix for _id being keypath...
			if(event.oldVersion < 2) {
				db.deleteObjectStore('documents');
				var documents = db.createObjectStore('documents', {keyPath: '$collectionKey', unique: true});
				documents.createIndex('$collection', '$collection', {unique:false});
			}
			//self._documents.createIndex('streamIdCommitSequence', 'streamIdCommitSequence', {unique:true});
			//db.createObjectStore('commits', {keyPath: 'id'});
		}
		request.onerror = function(event) {
			reject(new Error(event));
		}
		request.onblocked = function(event) {
			reject(new Error(event));
		}
	}).nodeify(callback);
}

Store.prototype.collection = function(name, callback) {
	var collection = this._collections[name];
	if(!collection) {
		collection = this._collections[name] = new Collection(this._db, name);
	}
	if(callback) {
		callback(collection);
	}
	return collection;
}

module.exports = Store;<|endoftext|>


<commit_before>// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
'use strict';

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'client/bower_components/jquery/dist/jquery.js',
      'client/bower_components/angular/angular.js',
      'client/bower_components/angular-mocks/angular-mocks.js',
      'client/bower_components/angular-resource/angular-resource.js',
      'client/bower_components/angular-cookies/angular-cookies.js',
      'client/bower_components/angular-sanitize/angular-sanitize.js',
      'client/bower_components/angular-route/angular-route.js',
      'client/bower_components/angular-aria/angular-aria.js',
      'client/bower_components/angular-animate/angular-animate.js',
      'client/bower_components/angular-material/angular-material.js',
      'client/bower_components/angular-google-chart/ng-google-chart.js',
      'client/bower_components/angular-google-maps/dist/angular-google-maps.js',
      'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
      'client/bower_components/ngGeolocation/ngGeolocation.js',
      'client/bower_components/angular-linkify/angular-linkify.js',
      'client/bower_components/angularjs-viewhead/angularjs-viewhead.js',
      'client/bower_components/angular-qr/src/angular-qr.js',
      'client/bower_components/lodash/lodash.js',
      'client/app/firefly.module.js',
      'client/app/firefly.config.js',
      'client/app/**/*.js',
      'client/components/**/*.js',
      'client/app/**/*.html',
      'client/components/**/*.html'
    ],

    preprocessors: {
      '**/*.jade': 'ng-jade2js',
      '**/*.html': 'html2js'
    },

    ngHtml2JsPreprocessor: {
      stripPrefix: 'client/'
    },

    ngJade2JsPreprocessor: {
      stripPrefix: 'client/'
    },

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['PhantomJS'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};
<commit_msg>fix(tests): update new library path in `karma.config.js`.<commit_after>// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
'use strict';

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      'client/bower_components/jquery/dist/jquery.js',
      'client/bower_components/angular/angular.js',
      'client/bower_components/angular-mocks/angular-mocks.js',
      'client/bower_components/angular-resource/angular-resource.js',
      'client/bower_components/angular-cookies/angular-cookies.js',
      'client/bower_components/angular-sanitize/angular-sanitize.js',
      'client/bower_components/angular-route/angular-route.js',
      'client/bower_components/angular-aria/angular-aria.js',
      'client/bower_components/angular-animate/angular-animate.js',
      'client/bower_components/angular-material/angular-material.js',
      'client/bower_components/angular-google-chart/ng-google-chart.js',
      'client/bower_components/angular-google-maps/dist/angular-google-maps.js',
      'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
      'client/bower_components/ngGeolocation/ngGeolocation.js',
      'client/bower_components/angular-linkify/angular-linkify.js',
      'client/bower_components/angularjs-viewhead/angularjs-viewhead.js',
      'client/bower_components/devintent-qr/src/angular-qr.js',
      'client/bower_components/lodash/lodash.js',
      'client/app/firefly.module.js',
      'client/app/firefly.config.js',
      'client/app/**/*.js',
      'client/components/**/*.js',
      'client/app/**/*.html',
      'client/components/**/*.html'
    ],

    preprocessors: {
      '**/*.jade': 'ng-jade2js',
      '**/*.html': 'html2js'
    },

    ngHtml2JsPreprocessor: {
      stripPrefix: 'client/'
    },

    ngJade2JsPreprocessor: {
      stripPrefix: 'client/'
    },

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['PhantomJS'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};
<|endoftext|>


<commit_before>window.requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function (/* function */ callback, /* DOMElement */ element) {
                window.setTimeout(callback, 1000 / 60);
            };
})();

Animation = function (spriteSheet, startX, startY, frameWidth, frameHeight, frameDuration, frames, loop, reverse) {
    this.spriteSheet = spriteSheet;
    this.startX = startX;
    this.startY = startY;
    this.frameWidth = frameWidth;
    this.frameHeight = frameHeight;
    this.frameDuration = frameDuration;
    this.frames = frames;
    this.totalTime = frameDuration * frames;
    this.elapsedTime = 0;
    this.loop = loop;
    this.reverse = reverse;
}

Animation.prototype.drawFrame = function (tick, context, x, y, scaleBy) {
    this.elapsedTime += tick;
    var scaleBy = scaleBy || 1;
    if (this.loop) {
        if (this.isDone()) {
            this.elapsedTime = 0;
        }
    } else if (this.isDone()) {
        return;
    }
    var index = 0;
    var that = this;
    if (this.reverse) {
        index = that.frames - that.currentFrame() - 1;
    } else {
        index = that.currentFrame();
    }

    var locX = x;
    var locY = y;
    context.drawImage(this.spriteSheet,
                      index * this.frameWidth, this.startY * this.frameHeight,
                      this.frameWidth, this.frameHeight,
                      locX, locY,
                      this.frameWidth * scaleBy, this.frameHeight * scaleBy);
}

Animation.prototype.currentFrame = function () {
    return Math.floor(this.elapsedTime / this.frameDuration);
}

Animation.prototype.isDone = function () {
    return (this.elapsedTime >= this.totalTime);
}

GameEngine = function () {
    this.entities = [];
    this.context = null;
    this.width = null;
    this.height = null;
    this.timer = null;
    this.key = null;
    this.space = null;
}

GameEngine.prototype.init = function (context) {
    this.context = context;
    this.width = this.context.canvas.width;
    this.height = this.context.canvas.height;
    this.timer = new Timer();
    this.startInput();
}

GameEngine.prototype.startInput = function () {
    var that = this;

    this.context.canvas.addEventListener("keydown", function (e) {
        that.key = null;
        that.space = null;
        if (String.fromCharCode(e.which) === ' ') {
            that.space = true;
        } else if (String.fromCharCode(e.which) === '37'
                   || String.fromCharCode(e.which) === '38'
                   || String.fromCharCode(e.which) === '39'
                   || String.fromCharCode(e.which) === '40') {
            that.key = String.fromCharCode(e.which);
        }
        e.preventDefault();
    }, false);

    this.context.canvas.addEventListener("keyup", function (e) {
        that.key = null;
        that.space = null;
    }, false);
}

GameEngine.prototype.start = function () {
    var that = this;
    (gameLoop = function () {
        that.loop();
        requestAnimationFrame(gameLoop, that.context.canvas);
    })();
}

GameEngine.prototype.addEntity = function (entity) {
    this.entities.push(entity);
}

GameEngine.prototype.draw = function (drawCallBack) {
    this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
    this.context.save();

    for (var i = 0; i < this.entities.length; i++) {
        this.entities[i].draw(this.context);
    }
    if (drawCallBack) {
        drawCallBack(this);
    }
    this.context.restore();
}

GameEngine.prototype.update = function () {
    for (var i = 0; i < this.entities.length; i++) {
        this.entities[i].update();
    }
}

GameEngine.prototype.loop = function () {
    this.clockTick = this.timer.tick();
    this.update();
    this.draw();
}

Timer = function () {
    this.gameTime = 0;
    this.maxStep = 0.5;
    this.lastTimeStep = 0;
}

Timer.prototype.tick = function () {
    var wallCurrent = Date.now();
    var wallDelta = (wallCurrent - this.lastTimeStep) / 1000;
    this.lastTimeStep = wallCurrent;

    var gameDelta = Math.min(wallDelta, this.maxStep);
    this.gameTime += gameDelta;
    return gameDelta;
}

var Direction = {
    UP: { value: "up", code: "38" },
    LEFT: { value: "left", code: "37" },
    DOWN: { value: "down", code: "40" },
    RIGHT: { value: "right", code: "39" }
}
Object.freeze(Direction);

Entity = function (game, x, y, spriteSheet) {
    this.game = game;
    this.x = x;
    this.y = y;
    this.direction = Direction.DOWN;
    this.moveRight = true;
    this.health;
    this.down_animation = new Animation(spriteSheet, 0, 10, 64, 64, 0.05, 9, true, false);
    this.up_animation = new Animation(spriteSheet, 0, 8, 64, 64, 0.05, 9, true, false);
    this.left_animation = new Animation(spriteSheet, 0, 9, 64, 64, 0.05, 9, true, false);
    this.right_animation = new Animation(spriteSheet, 0, 11, 64, 64, 0.05, 9, true, false);
}

/* ENTITY - Super class to the heroes, npcs, and enemies. */
Entity.prototype.draw = function (context) {
    var direction_animation = null;
    switch (this.direction) {
        case Direction.DOWN:
            direction_animation = this.down_animation;
            break;
        case Direction.UP:
            direction_animation = this.up_animation;
            break;
        case Direction.LEFT:
            direction_animation = this.left_animation;
            break;
        case Direction.RIGHT:
            direction_animation = this.right_animation;
            break;
        default:
            direction_animation = this.down_animation;
    }
    direction_animation.drawFrame(this.game.clockTick, context, this.x, this.y); Entity.prototype.draw.call(this, context);
}

Entity.prototype.update = function () {
    if (this.game.space) {
        // code for selecting something with the space bar. I don't think this will be necessary for the prototype.
    } else if (this.game.key === '37') { // left
        this.direction = Direction.LEFT;
    } else if (this.game.key === '38') { // up
        this.direction = Direction.UP;
    } else if (this.game.key === '39') { // right
        this.direction = Direction.RIGHT;
    } else if (this.game.key === '40') { // down
        this.direction = Direction.DOWN;
    }
    switch (this.Direction) {
        case Direction.DOWN:
            this.y += 2;
            break;
        case Direction.UP:
            this.y -= 2;
            break;
        case Direction.LEFT:
            this.x -= 2;
            break;
        case Direction.RIGHT:
            this.y += 2;
            break;
        default:;
    }
}

Entity.prototype.reset = function () {

}

/* HERO subclasses */

Warrior = function (game) {
    this.game = game;
    this.spriteSheet = "./imgs/warrior.png";
    this.x = 50;
    this.y = 50;
    Entity.call(this, game, this.x, this.y, this.spriteSheet);
}

Warrior.prototype = new Entity();
Warrior.prototype.constructor = Warrior;

Warrior.prototype.draw = function (context) {
    Entity.prototype.draw(this, context);
}

Warrior.prototype.update = function () {
    Entity.prototype.update.call(this);
}

/* ENEMY and subclasses */

/* NPC */

<commit_msg>Working on control input<commit_after>window.requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function (/* function */ callback, /* DOMElement */ element) {
                window.setTimeout(callback, 1000 / 60);
            };
})();

Animation = function (spriteSheet, startX, startY, frameWidth, frameHeight, frameDuration, frames, loop, reverse) {
    this.spriteSheet = spriteSheet;
    this.startX = startX;
    this.startY = startY;
    this.frameWidth = frameWidth;
    this.frameHeight = frameHeight;
    this.frameDuration = frameDuration;
    this.frames = frames;
    this.totalTime = frameDuration * frames;
    this.elapsedTime = 0;
    this.loop = loop;
    this.reverse = reverse;
}

Animation.prototype.drawFrame = function (tick, context, x, y, scaleBy) {
    this.elapsedTime += tick;
    var scaleBy = scaleBy || 1;
    if (this.loop) {
        if (this.isDone()) {
            this.elapsedTime = 0;
        }
    } else if (this.isDone()) {
        return;
    }
    var index = 0;
    var that = this;
    if (this.reverse) {
        index = that.frames - that.currentFrame() - 1;
    } else {
        index = that.currentFrame();
    }

    var locX = x;
    var locY = y;
    context.drawImage(this.spriteSheet,
                      index * this.frameWidth, this.startY * this.frameHeight,
                      this.frameWidth, this.frameHeight,
                      locX, locY,
                      this.frameWidth * scaleBy, this.frameHeight * scaleBy);
}

Animation.prototype.currentFrame = function () {
    return Math.floor(this.elapsedTime / this.frameDuration);
}

Animation.prototype.isDone = function () {
    return (this.elapsedTime >= this.totalTime);
}

GameEngine = function () {
    this.entities = [];
    this.context = null;
    this.width = null;
    this.height = null;
    this.timer = null;
    this.key = null;
    this.space = null;
}

GameEngine.prototype.init = function (context) {
    this.context = context;
    this.width = this.context.canvas.width;
    this.height = this.context.canvas.height;
    this.timer = new Timer();
    this.startInput();
}

GameEngine.prototype.startInput = function () {
    var that = this;

    this.context.canvas.addEventListener("keydown", function (e) {
        that.key = null;
        that.space = null;
        if (String.fromCharCode(e.which) === ' ') {
            that.space = true;
        } else if (String.fromCharCode(e.which) === '37'
                   || String.fromCharCode(e.which) === '38'
                   || String.fromCharCode(e.which) === '39'
                   || String.fromCharCode(e.which) === '40') {
            that.key = String.fromCharCode(e.which);
        }
        e.preventDefault();
    }, false);

    this.context.canvas.addEventListener("keyup", function (e) {
        that.key = null;
        that.space = null;
    }, false);
}

GameEngine.prototype.start = function () {
    var that = this;
    (gameLoop = function () {
        that.loop();
        requestAnimationFrame(gameLoop, that.context.canvas);
    })();
}

GameEngine.prototype.addEntity = function (entity) {
    this.entities.push(entity);
}

GameEngine.prototype.draw = function (drawCallBack) {
    this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
    this.context.save();

    for (var i = 0; i < this.entities.length; i++) {
        this.entities[i].draw(this.context);
    }
    if (drawCallBack) {
        drawCallBack(this);
    }
    this.context.restore();
}

GameEngine.prototype.update = function () {
    for (var i = 0; i < this.entities.length; i++) {
        this.entities[i].update();
    }
}

GameEngine.prototype.loop = function () {
    this.clockTick = this.timer.tick();
    this.update();
    this.draw();
}

Timer = function () {
    this.gameTime = 0;
    this.maxStep = 0.5;
    this.lastTimeStep = 0;
}

Timer.prototype.tick = function () {
    var wallCurrent = Date.now();
    var wallDelta = (wallCurrent - this.lastTimeStep) / 1000;
    this.lastTimeStep = wallCurrent;

    var gameDelta = Math.min(wallDelta, this.maxStep);
    this.gameTime += gameDelta;
    return gameDelta;
}

var Direction = {
    UP: { value: "up", code: "38" },
    LEFT: { value: "left", code: "37" },
    DOWN: { value: "down", code: "40" },
    RIGHT: { value: "right", code: "39" }
}
Object.freeze(Direction);

Entity = function (game, x, y, spriteSheet) {
    this.game = game;
    this.x = x;
    this.y = y;
    this.direction = Direction.DOWN;
    this.moveRight = true;
    this.health;
    this.down_animation = new Animation(spriteSheet, 0, 10, 64, 64, 0.05, 9, true, false);
    this.up_animation = new Animation(spriteSheet, 0, 8, 64, 64, 0.05, 9, true, false);
    this.left_animation = new Animation(spriteSheet, 0, 9, 64, 64, 0.05, 9, true, false);
    this.right_animation = new Animation(spriteSheet, 0, 11, 64, 64, 0.05, 9, true, false);
}

/* ENTITY - Super class to the heroes, npcs, and enemies. */
Entity.prototype.draw = function (context) {
    var direction_animation = this.down_animation;
    switch (this.direction) {
        case Direction.DOWN:
            direction_animation = this.down_animation;
            break;
        case Direction.UP:
            direction_animation = this.up_animation;
            break;
        case Direction.LEFT:
            direction_animation = this.left_animation;
            break;
        case Direction.RIGHT:
            direction_animation = this.right_animation;
            break;
        default:
            direction_animation = this.down_animation;
    }
    direction_animation.drawFrame(this.game.clockTick, context, this.x, this.y);
}

Entity.prototype.update = function () {
    if (this.game.space) {
        // code for selecting something with the space bar. I don't think this will be necessary for the prototype.
    } else if (this.game.key === '37') { // left
        this.direction = Direction.LEFT;
    } else if (this.game.key === '38') { // up
        this.direction = Direction.UP;
    } else if (this.game.key === '39') { // right
        this.direction = Direction.RIGHT;
    } else if (this.game.key === '40') { // down
        this.direction = Direction.DOWN;
    }
    switch (this.Direction) {
        case Direction.DOWN:
            this.y += 2;
            break;
        case Direction.UP:
            this.y -= 2;
            break;
        case Direction.LEFT:
            this.x -= 2;
            break;
        case Direction.RIGHT:
            this.y += 2;
            break;
        default:;
    }
}

Entity.prototype.reset = function () {

}

/* HERO subclasses */

Warrior = function (game) {
    this.game = game;
    this.spriteSheet = ASSET_MANAGER.getAsset("./imgs/warrior.png");
    this.x = 50;
    this.y = 50;
    Entity.call(this, game, this.x, this.y, this.spriteSheet);
}

Warrior.prototype = new Entity();
Warrior.prototype.constructor = Warrior;

Warrior.prototype.draw = function (context) {
    Entity.prototype.draw.call(this, context);
}

Warrior.prototype.update = function () {
    Entity.prototype.update.call(this);
}

/* ENEMY and subclasses */

/* NPC */

<|endoftext|>


<commit_before>"use strict";

const frameworks = ["mocha"];
const reporters = ["coverage-istanbul", "progress"];
const files = ["test/test_index.js"];
const preprocessors = {
    "test/test_index.js": ["webpack", "sourcemap"]
};
const webpack = require("./webpack.config");
const beforeMiddleware = ["webpackBlocker"];
let concurrency = Infinity;
const browsers = [];
const customLaunchers = {};
const browserStack = {
    project: "frame_" + require("child_process").execSync("git branch | grep \\* | cut -d \" \" -f2-")
};

if (process.env.BROWSER_STACK_USERNAME) {
    reporters.push("BrowserStack");
    concurrency = 1;
    const bsCaps = require("browserstack-capabilities")(process.env.BROWSER_STACK_USERNAME, process.env.BROWSER_STACK_ACCESS_KEY);
    const capabilities = bsCaps.create({
        "browser": ["chrome", "firefox"],
        "browser_version": "latest"
    });
    capabilities.forEach((capability) => {
        const browser = Object.keys(capability).map((key) => capability[key]).filter((str) => str).join();
        browsers.push(browser);
        capability.base = "BrowserStack";
        customLaunchers[browser] = capability;
    });
}

if (process.env.CIRCLECI) {
    browsers.sort();
    const browserNum = Math.ceil(browsers.length / process.env.CIRCLE_NODE_TOTAL);
    browsers.splice(0, browserNum * process.env.CIRCLE_NODE_INDEX);
    browsers.splice(browserNum * (process.env.CIRCLE_NODE_INDEX + 1));
    browserStack.project = process.env.CIRCLE_PROJECT_REPONAME + "_" + process.env.CIRCLE_BRANCH;
}

module.exports = (config) => {
    config.set({
        frameworks,
        reporters,
        files,
        preprocessors,
        webpack,
        beforeMiddleware,
        concurrency,
        browsers,
        browserStack,
        customLaunchers
    });
};
<commit_msg>テストブラウザを増やしました。<commit_after>"use strict";

module.exports = (config) => {

    const frameworks = ["mocha"];
    const reporters = ["coverage-istanbul", "progress"];
    const files = ["test/test_index.js"];
    const preprocessors = {
        "test/test_index.js": ["webpack", "sourcemap"]
    };
    const webpack = require("./webpack.config");
    const beforeMiddleware = ["webpackBlocker"];
    let concurrency = Infinity;
    const browsers = [];
    const customLaunchers = {};
    const browserStack = {
        project: "frame_" + require("child_process").execSync("git branch | grep \\* | cut -d \" \" -f2-")
    };

    if (config.singleRun && process.env.BROWSER_STACK_USERNAME) {
        reporters.push("BrowserStack");
        concurrency = 1;
        const bsCaps = require("browserstack-capabilities")(process.env.BROWSER_STACK_USERNAME, process.env.BROWSER_STACK_ACCESS_KEY);
        const capabilities = bsCaps.create({
            "browser": ["chrome", "firefox", "ie", "opera", "edge", "safari"],
            "browser_version": "latest",
            "os": ["Windows", "OS X"],
            "os_version": ["10", "8.1", "8", "7", "El Capitan", "Sierra"]
        }).filter((capability) => capability);
        capabilities.forEach((capability) => {
            const browser = [
                "os",
                "os_version",
                "browser",
                "browser_version"
            ].map((key) => capability[key]).join(" ");
            browsers.push(browser);
            capability.base = "BrowserStack";
            customLaunchers[browser] = capability;
        });
    }

    if (process.env.CIRCLECI) {
        browsers.sort();
        const browserNum = Math.ceil(browsers.length / process.env.CIRCLE_NODE_TOTAL);
        browsers.splice(0, browserNum * process.env.CIRCLE_NODE_INDEX);
        browsers.splice(browserNum * (process.env.CIRCLE_NODE_INDEX + 1));
        browserStack.project = process.env.CIRCLE_PROJECT_REPONAME + "_" + process.env.CIRCLE_BRANCH;
    }
    else {
        browserStack.build = Date.now();
    }

    config.set({
        frameworks,
        reporters,
        files,
        preprocessors,
        webpack,
        beforeMiddleware,
        concurrency,
        browsers,
        browserStack,
        customLaunchers
    });
};
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const async = require('async');

// Local dependencies.
const regex = require('../../../../node_modules/strapi/util/regex');

/**
 * Creates Routes.
 */

exports.create = function () {
  const deferred = Promise.defer();
  const promises = [];
  const newRoutes = [];
  let routesFound;

  async.auto({
    findRoutes: function (callback) {

      // Find all routes.
      strapi.orm
        .collections
        .route
        .find()
        .exec(function (err, routesFound) {
          if (err) {
            callback(err);
          } else {
            callback(null, routesFound);
          }
        });
    },
    deleteRoutes: ['findRoutes', function (callback, results) {

      // Async dependencies.
      routesFound = results.findRoutes;

      // Delete destroyed routes.
      _.forEach(routesFound, function (routeFound) {
        if (!strapi.config.routes[routeFound.name]) {
          promises.push(strapi.orm.collections.route.destroy({id: routeFound.id}));
        }
      });

      callback(null);
    }],
    updateOrCreateRoutes: ['findRoutes', function (callback, results) {

      // Async dependencies.
      routesFound = results.findRoutes;
      let verb;

      // Find or create routes.
      _.forEach(strapi.config.routes, function (route, key) {
        verb = regex.detectRoute(key).verb;

        if (_.find(routesFound, {name: key})) {
          promises.push(strapi.orm.collections.route.update({
            name: key
          }, {
            name: key,
            policies: route.policies,
            controller: route.controller,
            action: route.action,
            verb: verb
          }));
        } else {
          newRoutes.push(key);
          promises.push(strapi.orm.collections.route.create({
            name: key,
            policies: route.policies,
            controller: route.controller,
            action: route.action,
            verb: verb
          }));
        }
      });

      callback(null);
    }],
    execRoutesModifications: ['deleteRoutes', 'updateOrCreateRoutes', function (callback) {

      // Exec the promises.
      Promise.all(promises)
        .then(function (responses) {
          callback(null, responses);
        })
        .catch(function (err) {
          callback(err);
        });
    }],

    findNewRoutes: ['execRoutesModifications', function (callback) {

      // Find created routes.
      strapi.orm
        .collections
        .route
        .find({
          'name': newRoutes
        })
        .populate('roles')
        .exec(function (err, newRoutesFound) {
          if (err) {
            callback(err);
          } else {
            callback(null, newRoutesFound);
          }
        });
    }],
    updateCreatedRoutes: ['execRoutesModifications', 'findNewRoutes', 'findRoles', function (callback, results) {

      // Async dependencies.
      const newRoutesFound = results.findNewRoutes;
      const roles = results.findRoles;

      const userContributorRoutes = [
        'GET /user/:id',
        'PUT /user/:id',
        'DELETE /user/:id'
      ];

      const userRegisteredRoutes = [
        'PUT /user/:id',
        'DELETE /user/:id'
      ];

      let verb;

      const adminRole = _.find(roles, {
        name: 'admin'
      });

      _.forEach(newRoutesFound, function (newRoute) {
        if (!_.contains(newRoute.name, '/dashboard')) {

          // Contributor permissions.
          verb = regex.detectRoute(newRoute.name).verb;
          newRoute.verb = verb;
          newRoute.isPublic = false;
          newRoute.registeredAuthorized = false;
          newRoute.contributorsAuthorized = false;

          if (_.contains(newRoute.name, '/auth')) {
            newRoute.isPublic = true;
          } else if (_.contains(newRoute.name, '/user')) {
            if (_.contains(userContributorRoutes, newRoute.name)) {
              newRoute.contributorsAuthorized = true;
            }
            if (_.contains(userRegisteredRoutes, newRoute.name)) {
              newRoute.registeredAuthorized = true;
            }
          } else {
            if (verb === 'get') {
              newRoute.isPublic = true;
              newRoute.registeredAuthorized = true;
            }

            newRoute.contributorsAuthorized = true;
          }

          newRoute.roles.add(adminRole.id);

          promises.push(new Promise(function (resolve, reject) {
            newRoute.save(function (err) {
              if (err) {
                reject(err);
              }
            });

            resolve();
          }));
        }
      });

      Promise.all(promises)
        .then(function (newRoutes) {
          callback(null, newRoutes);
        })
        .catch(function (err) {
          callback(err);
        });

    }],
    findRoles: [function (callback) {

      // Find roles.
      strapi.orm
        .collections
        .role
        .find()
        .exec(function (err, roles) {
          if (err) {
            callback(err);
          } else {
            callback(null, roles);
          }
        });
    }]
  }, function cb(err, results) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve(results);
    }
  });

  return deferred.promise;
};
<commit_msg>Detect if the controller is a stringified function<commit_after>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const async = require('async');

// Local dependencies.
const regex = require('../../../../node_modules/strapi/util/regex');

/**
 * Creates Routes.
 */

exports.create = function () {
  const deferred = Promise.defer();
  const promises = [];
  const newRoutes = [];
  let routesFound;

  async.auto({
    findRoutes: function (callback) {

      // Find all routes.
      strapi.orm
        .collections
        .route
        .find()
        .exec(function (err, routesFound) {
          if (err) {
            callback(err);
          } else {
            callback(null, routesFound);
          }
        });
    },
    deleteRoutes: ['findRoutes', function (callback, results) {

      // Async dependencies.
      routesFound = results.findRoutes;

      // Delete destroyed routes.
      _.forEach(routesFound, function (routeFound) {
        if (!strapi.config.routes[routeFound.name]) {
          promises.push(strapi.orm.collections.route.destroy({id: routeFound.id}));
        }
      });

      callback(null);
    }],
    updateOrCreateRoutes: ['findRoutes', function (callback, results) {

      // Async dependencies.
      routesFound = results.findRoutes;
      let verb;

      // Find or create routes.
      _.forEach(strapi.config.routes, function (route, key) {
        verb = regex.detectRoute(key).verb;

        // Check if the controller is a stringified function.
        route.controller = _.startsWith(route.controller, 'function') ? 'Specific function' : route.controller;

        if (_.find(routesFound, {name: key})) {
          promises.push(strapi.orm.collections.route.update({
            name: key
          }, {
            name: key,
            policies: route.policies,
            controller: route.controller,
            action: route.action,
            verb: verb
          }));
        } else {
          newRoutes.push(key);
          promises.push(strapi.orm.collections.route.create({
            name: key,
            policies: route.policies,
            controller: route.controller,
            action: route.action,
            verb: verb
          }));
        }
      });

      callback(null);
    }],
    execRoutesModifications: ['deleteRoutes', 'updateOrCreateRoutes', function (callback) {

      // Exec the promises.
      Promise.all(promises)
        .then(function (responses) {
          callback(null, responses);
        })
        .catch(function (err) {
          callback(err);
        });
    }],

    findNewRoutes: ['execRoutesModifications', function (callback) {

      // Find created routes.
      strapi.orm
        .collections
        .route
        .find({
          'name': newRoutes
        })
        .populate('roles')
        .exec(function (err, newRoutesFound) {
          if (err) {
            callback(err);
          } else {
            callback(null, newRoutesFound);
          }
        });
    }],
    updateCreatedRoutes: ['execRoutesModifications', 'findNewRoutes', 'findRoles', function (callback, results) {

      // Async dependencies.
      const newRoutesFound = results.findNewRoutes;
      const roles = results.findRoles;

      const userContributorRoutes = [
        'GET /user/:id',
        'PUT /user/:id',
        'DELETE /user/:id'
      ];

      const userRegisteredRoutes = [
        'PUT /user/:id',
        'DELETE /user/:id'
      ];

      let verb;

      const adminRole = _.find(roles, {
        name: 'admin'
      });

      _.forEach(newRoutesFound, function (newRoute) {
        if (!_.contains(newRoute.name, '/dashboard')) {

          // Contributor permissions.
          verb = regex.detectRoute(newRoute.name).verb;
          newRoute.verb = verb;
          newRoute.isPublic = false;
          newRoute.registeredAuthorized = false;
          newRoute.contributorsAuthorized = false;

          if (_.contains(newRoute.name, '/auth')) {
            newRoute.isPublic = true;
          } else if (_.contains(newRoute.name, '/user')) {
            if (_.contains(userContributorRoutes, newRoute.name)) {
              newRoute.contributorsAuthorized = true;
            }
            if (_.contains(userRegisteredRoutes, newRoute.name)) {
              newRoute.registeredAuthorized = true;
            }
          } else {
            if (verb === 'get') {
              newRoute.isPublic = true;
              newRoute.registeredAuthorized = true;
            }

            newRoute.contributorsAuthorized = true;
          }

          newRoute.roles.add(adminRole.id);

          promises.push(new Promise(function (resolve, reject) {
            newRoute.save(function (err) {
              if (err) {
                reject(err);
              }
            });

            resolve();
          }));
        }
      });

      Promise.all(promises)
        .then(function (newRoutes) {
          callback(null, newRoutes);
        })
        .catch(function (err) {
          callback(err);
        });

    }],
    findRoles: [function (callback) {

      // Find roles.
      strapi.orm
        .collections
        .role
        .find()
        .exec(function (err, roles) {
          if (err) {
            callback(err);
          } else {
            callback(null, roles);
          }
        });
    }]
  }, function cb(err, results) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve(results);
    }
  });

  return deferred.promise;
};
<|endoftext|>


<commit_before>import { fragmentFrom } from "../core/htmlLiterals.js";
import ReactiveElement from "../core/ReactiveElement.js";
import { transmute } from "../core/template.js";
import DisabledMixin from "./DisabledMixin.js";
import FocusVisibleMixin from "./FocusVisibleMixin.js";
import {
  defaultState,
  firstRender,
  ids,
  inputDelegate,
  raiseChangeEvents,
  render,
  rendered,
  setState,
  shadowRoot,
  state,
  stateEffects,
  template,
} from "./internal.js";
import LanguageDirectionMixin from "./LanguageDirectionMixin.js";
import layoutPopup from "./layoutPopup.js";
import OpenCloseMixin from "./OpenCloseMixin.js";
import Popup from "./Popup.js";

const resizeListenerKey = Symbol("resizeListener");

const Base = DisabledMixin(
  FocusVisibleMixin(LanguageDirectionMixin(OpenCloseMixin(ReactiveElement)))
);

/**
 * Positions a popup with respect to a source element
 *
 * @inherits ReactiveElement
 * @mixes DisabledMixin
 * @mixes FocusVisibleMixin
 * @mixes OpenCloseMixin
 * @part {Popup} popup - the popup element
 * @part {button} source - the element used as the reference point for positioning the popup, generally the element that invokes the popup
 */
class PopupSource extends Base {
  // @ts-ignore
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      ariaHasPopup: "true",
      popupAlign: "start",
      popupDirection: "column",
      popupLayout: null,
      popupPartType: Popup,
      sourcePartType: "div",
    });
  }

  // By default, assume that the source part is an input-like element that will
  // get the foucs.
  get [inputDelegate]() {
    return this[ids].source;
  }

  get frame() {
    return /** @type {any} */ (this[ids].popup).frame;
  }

  // TODO: Remove this deprecated property.
  get horizontalAlign() {
    return this[state].popupAlign;
  }
  set horizontalAlign(horizontalAlign) {
    console.warn(
      `The "horizontalAlign" property has been renamed to "popupAlign"; the "horizontal-align" attribute is now "popup-align".`
    );
    this[setState]({ popupAlign: horizontalAlign });
  }

  /**
   * The alignment of the popup with respect to the source button.
   *
   * * `bottom`: popup and source are bottom-aligned
   * * `end`: popup and source are aligned on the trailing edge according to the
   *   text direction
   * * `left`: popup and source are left-aligned
   * * `right`: popup and source are right-aligned
   * * `start`: popup and source are aligned on the leading edge according to
   *   the text direction
   * * `stretch`: both left and right edges are aligned
   * * `top`: popup and source are top-aligned
   *
   * @type {('bottom'|'end'|'left'|'right'|'start'|'stretch'|'top')}
   * @default 'start'
   */
  get popupAlign() {
    return this[state].popupAlign;
  }
  set popupAlign(popupAlign) {
    this[setState]({ popupAlign });
  }

  /**
   * The preferred direction for the popup.
   *
   * * `above`: popup appears above the source
   * * `below`: popup appears below the source
   * * `column-reverse`: popup appears before the source in the block axis
   * * `column`: popup appears after the source in the block axis
   * * `left`: popup appears to the left of the source
   * * `right`: popup appears to the right of the source
   * * `row-reverse`: popup appears before the source in the inline axis
   * * `row`: popup appears after the source in the inline axis
   *
   * @type {('above'|'below'|'column-reverse'|'column'|'left'|'right'|'row-reverse'|'row')}
   * @default 'column'
   */
  get popupDirection() {
    return this[state].popupDirection;
  }
  set popupDirection(popupDirection) {
    this[setState]({ popupDirection });
  }

  // TODO: Remove this deprecated property.
  get popupPosition() {
    return this[state].popupPosition;
  }
  set popupPosition(popupPosition) {
    console.warn(
      `The "popupPosition" property has been renamed to "popupDirection"; the "popup-position" attribute is now "popup-direction".`
    );
    this[setState]({ popupPosition });
  }

  /**
   * The class or tag used to create the `popup` part – the element
   * responsible for displaying the popup and handling overlay behavior.
   *
   * @type {PartDescriptor}
   * @default Popup
   */
  get popupPartType() {
    return this[state].popupPartType;
  }
  set popupPartType(popupPartType) {
    this[setState]({ popupPartType });
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);

    renderParts(this[shadowRoot], this[state], changed);

    if (this[firstRender] || changed.ariaHasPopup) {
      const { ariaHasPopup } = this[state];
      if (ariaHasPopup === null) {
        this[inputDelegate].removeAttribute("aria-haspopup");
      } else {
        this[inputDelegate].setAttribute(
          "aria-haspopup",
          this[state].ariaHasPopup
        );
      }
    }

    if (changed.popupPartType) {
      // Popup's opened state becomes our own opened state.
      this[ids].popup.addEventListener("open", () => {
        if (!this.opened) {
          this[raiseChangeEvents] = true;
          this.open();
          this[raiseChangeEvents] = false;
        }
      });

      // Popup's closed state becomes our own closed state.
      this[ids].popup.addEventListener("close", (event) => {
        if (!this.closed) {
          this[raiseChangeEvents] = true;
          /** @type {any} */

          const cast = event;
          const closeResult = cast.detail.closeResult;
          this.close(closeResult);
          this[raiseChangeEvents] = false;
        }
      });
    }

    if (changed.opened || changed.popupLayout) {
      const { opened, popupLayout } = this[state];

      if (!opened) {
        // Popup closed. Reset the styles used to position it.
        Object.assign(this[ids].popup.style, {
          inset: "",
          opacity: "",
        });
      } else if (!popupLayout) {
        // Popup opened but not yet laid out.
        //
        // Render the component invisibly so we can measure it before showing
        // it. We hide it by giving it zero opacity. If we use `visibility:
        // hidden` for this purpose, the popup won't be able to receive the
        // focus, which would complicate our overlay focus handling.
        //
        // In case the popup is being relayed out because a layout-affecting
        // property changed while the popup is open, we reset the positiong
        // styles too.
        Object.assign(this[ids].popup.style, {
          inset: "",
          opacity: 0,
        });
      } else {
        // Popup opened and laid out. Position the popup using only the edges
        // implicated in the layout.
        const popup = this[ids].popup;
        const positionStyling = getPositiongStylingForLayout(popupLayout);
        Object.assign(popup.style, positionStyling, {
          opacity: "",
        });
      }
    }

    if (changed.opened) {
      const { opened } = this[state];
      /** @type {any} */ (this[ids].popup).opened = opened;
    }

    if (changed.disabled) {
      if ("disabled" in this[ids].source) {
        const { disabled } = this[state];
        /** @type {any} */ (this[ids].source).disabled = disabled;
      }
    }

    // Let the popup know its position relative to the source.
    if (changed.popupLayout) {
      const { popupLayout } = this[state];
      if (popupLayout) {
        const { align, direction } = popupLayout;
        /** @type {any} */ const popup = this[ids].popup;
        if ("position" in popup) {
          popup.position = direction;
        }
        if ("align" in popup) {
          popup.align = align;
        }
      }
    }
  }

  [rendered](/** @type {ChangedFlags} */ changed) {
    super[rendered](changed);
    const { opened } = this[state];
    if (changed.opened) {
      if (opened) {
        // Worth noting that's possible (but unusual) for a popup to render
        // opened on first render.
        waitThenRenderOpened(this);
      } else {
        removeEventListeners(this);
      }
    } else if (
      changed.popupLayout &&
      this[state].opened &&
      !this[state].popupLayout
    ) {
      // A layout-affecting property changed while the popup is open; do layout
      // again.
      choosePopupLayout(this);
    }
  }

  /**
   * The class or tag used to create the `source` part - the button
   * (or other element) the user can tap/click to invoke the popup.
   *
   * @type {PartDescriptor}
   * @default 'button'
   */
  get sourcePartType() {
    return this[state].sourcePartType;
  }
  set sourcePartType(sourcePartType) {
    this[setState]({ sourcePartType });
  }

  [stateEffects](state, changed) {
    const effects = super[stateEffects](state, changed);

    // We reset our popup calculations when the popup closes, or if it's open
    // and state that affects positioning has changed.
    if (
      (changed.opened && !state.opened) ||
      (state.opened &&
        (changed.popupAlign || changed.popupDirection || changed.rightToLeft))
    ) {
      Object.assign(effects, {
        popupLayout: null,
      });
    }

    return effects;
  }

  get [template]() {
    const result = super[template];
    result.content.append(fragmentFrom.html`

        <slot name="source"></slot>

        <slot></slot>

    `);

    renderParts(result.content, this[state]);

    return result;
  }
}

function addEventListeners(/** @type {PopupSource} */ element) {
  /** @type {any} */ const cast = element;
  cast[resizeListenerKey] = () => {
    // If viewport resizes while the popup is open, we may want to change which
    // layout we're using for the popup.
    choosePopupLayout(element);
  };
  const viewport = window.visualViewport || window;
  viewport.addEventListener("resize", cast[resizeListenerKey]);
}

/**
 * Based on the current size of the source, popup, and viewport, determine which
 * layout we'll use for the popup.
 *
 * @private
 * @param {PopupSource} element
 */
function choosePopupLayout(element) {
  const { popupAlign, popupDirection, rightToLeft } = element[state];
  const sourceRect = element[ids].source.getBoundingClientRect();
  const popupRect = element[ids].popup.getBoundingClientRect();
  const boundsRect = viewportBounds();

  const popupLayout = layoutPopup(sourceRect, popupRect, boundsRect, {
    align: popupAlign,
    direction: popupDirection,
    rightToLeft,
  });

  element[setState]({ popupLayout });
}

// Given a complete layout for a popup (including the rect), determine the
// styling that should be applied to the popup.
function getPositiongStylingForLayout(layout) {
  const { align, direction, rect } = layout;
  const bounds = viewportBounds();
  const styling = {};
  const vertical = direction === "above" || direction === "below";
  switch (direction) {
    case "above":
    case "below":
      styling.bottom = `${bounds.bottom - rect.bottom}px`;
      styling.top = `${rect.top}px`;
      break;

    case "left":
    case "right":
      styling.left = `${rect.left}px`;
      styling.right = `${bounds.right - rect.right}px`;
      break;
  }
  switch (align) {
    case "bottom":
    case "top":
      styling.top = `${rect.top}px`;
      styling.bottom = `${bounds.bottom - rect.bottom}px`;
      break;
    case "center":
    case "stretch":
      if (vertical) {
        styling.left = `${rect.left}px`;
        styling.right = `${bounds.right - rect.right}px`;
      } else {
        styling.bottom = `${bounds.bottom - rect.bottom}px`;
        styling.top = `${rect.top}px`;
      }
      break;
    case "left":
    case "right":
      styling.left = `${rect.left}px`;
      styling.right = `${bounds.right - rect.right}px`;
      break;
  }
  return styling;
}

function removeEventListeners(/** @type {PopupSource} */ element) {
  /** @type {any} */ const cast = element;
  if (cast[resizeListenerKey]) {
    const viewport = window.visualViewport || window;
    viewport.removeEventListener("resize", cast[resizeListenerKey]);
    cast[resizeListenerKey] = null;
  }
}

/**
 * Render parts for the template or an instance.
 *
 * @private
 * @param {DocumentFragment} root
 * @param {PlainObject} state
 * @param {ChangedFlags} [changed]
 */
function renderParts(root, state, changed) {
  if (!changed || changed.popupPartType) {
    const { popupPartType } = state;
    const popup = root.getElementById("popup");
    if (popup) {
      transmute(popup, popupPartType);
    }
  }
  if (!changed || changed.sourcePartType) {
    const { sourcePartType } = state;
    const source = root.getElementById("source");
    if (source) {
      transmute(source, sourcePartType);
    }
  }
}

// Determine the bounding rectangle of the viewport. We prefer the
// VisualViewport API where that's available, as that handles a pinch-zoomed
// viewport on mobile. If not availble (as of October 2020, Firefox), we fall
// back to using the window as the viewport.
function viewportBounds() {
  // @ts-ignore
  const viewport = window.visualViewport;
  const boundsRect = viewport
    ? new DOMRect(
        viewport.offsetLeft,
        viewport.offsetTop,
        viewport.width,
        viewport.height
      )
    : new DOMRect(0, 0, window.innerWidth, window.innerHeight);
  return boundsRect;
}

/**
 *
 * When a popup is first rendered, we let it render invisibly so that it doesn't
 * affect the page layout.
 *
 * We then wait, for two reasons:
 *
 * 1) We need to give the popup time to render invisibly. That lets us get the
 *    true size of the popup content.
 *
 * 2) Wire up events that can dismiss the popup. If the popup was opened because
 *    the user clicked something, that opening click event may still be bubbling
 *    up, and we only want to start listening after it's been processed.
 *    Along the same lines, if the popup caused the page to scroll, we don't
 *    want to immediately close because the page scrolled (only if the user
 *    scrolls).
 *
 * After waiting, we can take care of both of the above tasks.
 *
 * @private
 * @param {PopupSource} element
 */
function waitThenRenderOpened(element) {
  // Wait a tick to let the newly-opened component actually render.
  setTimeout(() => {
    // It's conceivable the popup was closed before the timeout completed,
    // so double-check that it's still opened before listening to events.
    if (element[state].opened) {
      choosePopupLayout(element);
      addEventListeners(element);
    }
  });
}

export default PopupSource;
<commit_msg>Use simpler (simplistic?) positioning of popup, directly following the rect created by the layout algorithm.<commit_after>import { fragmentFrom } from "../core/htmlLiterals.js";
import ReactiveElement from "../core/ReactiveElement.js";
import { transmute } from "../core/template.js";
import DisabledMixin from "./DisabledMixin.js";
import FocusVisibleMixin from "./FocusVisibleMixin.js";
import {
  defaultState,
  firstRender,
  ids,
  inputDelegate,
  raiseChangeEvents,
  render,
  rendered,
  setState,
  shadowRoot,
  state,
  stateEffects,
  template,
} from "./internal.js";
import LanguageDirectionMixin from "./LanguageDirectionMixin.js";
import layoutPopup from "./layoutPopup.js";
import OpenCloseMixin from "./OpenCloseMixin.js";
import Popup from "./Popup.js";

const resizeListenerKey = Symbol("resizeListener");

const Base = DisabledMixin(
  FocusVisibleMixin(LanguageDirectionMixin(OpenCloseMixin(ReactiveElement)))
);

/**
 * Positions a popup with respect to a source element
 *
 * @inherits ReactiveElement
 * @mixes DisabledMixin
 * @mixes FocusVisibleMixin
 * @mixes OpenCloseMixin
 * @part {Popup} popup - the popup element
 * @part {button} source - the element used as the reference point for positioning the popup, generally the element that invokes the popup
 */
class PopupSource extends Base {
  // @ts-ignore
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      ariaHasPopup: "true",
      popupAlign: "start",
      popupDirection: "column",
      popupLayout: null,
      popupPartType: Popup,
      sourcePartType: "div",
    });
  }

  // By default, assume that the source part is an input-like element that will
  // get the foucs.
  get [inputDelegate]() {
    return this[ids].source;
  }

  get frame() {
    return /** @type {any} */ (this[ids].popup).frame;
  }

  // TODO: Remove this deprecated property.
  get horizontalAlign() {
    return this[state].popupAlign;
  }
  set horizontalAlign(horizontalAlign) {
    console.warn(
      `The "horizontalAlign" property has been renamed to "popupAlign"; the "horizontal-align" attribute is now "popup-align".`
    );
    this[setState]({ popupAlign: horizontalAlign });
  }

  /**
   * The alignment of the popup with respect to the source button.
   *
   * * `bottom`: popup and source are bottom-aligned
   * * `end`: popup and source are aligned on the trailing edge according to the
   *   text direction
   * * `left`: popup and source are left-aligned
   * * `right`: popup and source are right-aligned
   * * `start`: popup and source are aligned on the leading edge according to
   *   the text direction
   * * `stretch`: both left and right edges are aligned
   * * `top`: popup and source are top-aligned
   *
   * @type {('bottom'|'end'|'left'|'right'|'start'|'stretch'|'top')}
   * @default 'start'
   */
  get popupAlign() {
    return this[state].popupAlign;
  }
  set popupAlign(popupAlign) {
    this[setState]({ popupAlign });
  }

  /**
   * The preferred direction for the popup.
   *
   * * `above`: popup appears above the source
   * * `below`: popup appears below the source
   * * `column-reverse`: popup appears before the source in the block axis
   * * `column`: popup appears after the source in the block axis
   * * `left`: popup appears to the left of the source
   * * `right`: popup appears to the right of the source
   * * `row-reverse`: popup appears before the source in the inline axis
   * * `row`: popup appears after the source in the inline axis
   *
   * @type {('above'|'below'|'column-reverse'|'column'|'left'|'right'|'row-reverse'|'row')}
   * @default 'column'
   */
  get popupDirection() {
    return this[state].popupDirection;
  }
  set popupDirection(popupDirection) {
    this[setState]({ popupDirection });
  }

  // TODO: Remove this deprecated property.
  get popupPosition() {
    return this[state].popupPosition;
  }
  set popupPosition(popupPosition) {
    console.warn(
      `The "popupPosition" property has been renamed to "popupDirection"; the "popup-position" attribute is now "popup-direction".`
    );
    this[setState]({ popupPosition });
  }

  /**
   * The class or tag used to create the `popup` part – the element
   * responsible for displaying the popup and handling overlay behavior.
   *
   * @type {PartDescriptor}
   * @default Popup
   */
  get popupPartType() {
    return this[state].popupPartType;
  }
  set popupPartType(popupPartType) {
    this[setState]({ popupPartType });
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);

    renderParts(this[shadowRoot], this[state], changed);

    if (this[firstRender] || changed.ariaHasPopup) {
      const { ariaHasPopup } = this[state];
      if (ariaHasPopup === null) {
        this[inputDelegate].removeAttribute("aria-haspopup");
      } else {
        this[inputDelegate].setAttribute(
          "aria-haspopup",
          this[state].ariaHasPopup
        );
      }
    }

    if (changed.popupPartType) {
      // Popup's opened state becomes our own opened state.
      this[ids].popup.addEventListener("open", () => {
        if (!this.opened) {
          this[raiseChangeEvents] = true;
          this.open();
          this[raiseChangeEvents] = false;
        }
      });

      // Popup's closed state becomes our own closed state.
      this[ids].popup.addEventListener("close", (event) => {
        if (!this.closed) {
          this[raiseChangeEvents] = true;
          /** @type {any} */

          const cast = event;
          const closeResult = cast.detail.closeResult;
          this.close(closeResult);
          this[raiseChangeEvents] = false;
        }
      });
    }

    if (changed.opened || changed.popupLayout) {
      const { opened, popupLayout } = this[state];

      // By default, we reset the styles used to position the popup.
      /** @type {any} */
      const styling = {
        height: "",
        left: "",
        opacity: "",
        top: "",
        width: "",
      };
      if (!opened) {
        // Popup closed. Use the reset above.
      } else if (!popupLayout) {
        // Popup opened but not yet laid out.
        //
        // Render the component invisibly so we can measure it before showing
        // it. We hide it by giving it zero opacity. If we use `visibility:
        // hidden` for this purpose, the popup won't be able to receive the
        // focus, which would complicate our overlay focus handling.
        //
        // In case the popup is being relayed out because a layout-affecting
        // property changed while the popup is open, we reset the positiong
        // styles too.
        styling.opacity = 0;
      } else {
        // Popup opened and laid out. Position the popup using only the edges
        // implicated in the layout.
        const { rect } = popupLayout;
        Object.assign(styling, {
          height: `${rect.height}px`,
          left: `${rect.left}px`,
          top: `${rect.top}px`,
          width: `${rect.width}px`,
        });
      }
      Object.assign(this[ids].popup.style, styling);
    }

    if (changed.opened) {
      const { opened } = this[state];
      /** @type {any} */ (this[ids].popup).opened = opened;
    }

    if (changed.disabled) {
      if ("disabled" in this[ids].source) {
        const { disabled } = this[state];
        /** @type {any} */ (this[ids].source).disabled = disabled;
      }
    }

    // Let the popup know its position relative to the source.
    if (changed.popupLayout) {
      const { popupLayout } = this[state];
      if (popupLayout) {
        const { align, direction } = popupLayout;
        /** @type {any} */ const popup = this[ids].popup;
        if ("position" in popup) {
          popup.position = direction;
        }
        if ("align" in popup) {
          popup.align = align;
        }
      }
    }
  }

  [rendered](/** @type {ChangedFlags} */ changed) {
    super[rendered](changed);
    const { opened } = this[state];
    if (changed.opened) {
      if (opened) {
        // Worth noting that's possible (but unusual) for a popup to render
        // opened on first render.
        waitThenRenderOpened(this);
      } else {
        removeEventListeners(this);
      }
    } else if (
      changed.popupLayout &&
      this[state].opened &&
      !this[state].popupLayout
    ) {
      // A layout-affecting property changed while the popup is open; do layout
      // again.
      choosePopupLayout(this);
    }
  }

  /**
   * The class or tag used to create the `source` part - the button
   * (or other element) the user can tap/click to invoke the popup.
   *
   * @type {PartDescriptor}
   * @default 'button'
   */
  get sourcePartType() {
    return this[state].sourcePartType;
  }
  set sourcePartType(sourcePartType) {
    this[setState]({ sourcePartType });
  }

  [stateEffects](state, changed) {
    const effects = super[stateEffects](state, changed);

    // We reset our popup calculations when the popup closes, or if it's open
    // and state that affects positioning has changed.
    if (
      (changed.opened && !state.opened) ||
      (state.opened &&
        (changed.popupAlign || changed.popupDirection || changed.rightToLeft))
    ) {
      Object.assign(effects, {
        popupLayout: null,
      });
    }

    return effects;
  }

  get [template]() {
    const result = super[template];
    result.content.append(fragmentFrom.html`

        <slot name="source"></slot>

        <slot></slot>

    `);

    renderParts(result.content, this[state]);

    return result;
  }
}

function addEventListeners(/** @type {PopupSource} */ element) {
  /** @type {any} */ const cast = element;
  cast[resizeListenerKey] = () => {
    // If viewport resizes while the popup is open, we may want to change which
    // layout we're using for the popup.
    choosePopupLayout(element);
  };
  const viewport = window.visualViewport || window;
  viewport.addEventListener("resize", cast[resizeListenerKey]);
}

/**
 * Based on the current size of the source, popup, and viewport, determine which
 * layout we'll use for the popup.
 *
 * @private
 * @param {PopupSource} element
 */
function choosePopupLayout(element) {
  const { popupAlign, popupDirection, rightToLeft } = element[state];
  const sourceRect = element[ids].source.getBoundingClientRect();
  const popupRect = element[ids].popup.getBoundingClientRect();
  const boundsRect = viewportBounds();

  const popupLayout = layoutPopup(sourceRect, popupRect, boundsRect, {
    align: popupAlign,
    direction: popupDirection,
    rightToLeft,
  });

  element[setState]({ popupLayout });
}

function removeEventListeners(/** @type {PopupSource} */ element) {
  /** @type {any} */ const cast = element;
  if (cast[resizeListenerKey]) {
    const viewport = window.visualViewport || window;
    viewport.removeEventListener("resize", cast[resizeListenerKey]);
    cast[resizeListenerKey] = null;
  }
}

/**
 * Render parts for the template or an instance.
 *
 * @private
 * @param {DocumentFragment} root
 * @param {PlainObject} state
 * @param {ChangedFlags} [changed]
 */
function renderParts(root, state, changed) {
  if (!changed || changed.popupPartType) {
    const { popupPartType } = state;
    const popup = root.getElementById("popup");
    if (popup) {
      transmute(popup, popupPartType);
    }
  }
  if (!changed || changed.sourcePartType) {
    const { sourcePartType } = state;
    const source = root.getElementById("source");
    if (source) {
      transmute(source, sourcePartType);
    }
  }
}

// Determine the bounding rectangle of the viewport. We prefer the
// VisualViewport API where that's available, as that handles a pinch-zoomed
// viewport on mobile. If not availble (as of October 2020, Firefox), we fall
// back to using the window as the viewport.
function viewportBounds() {
  // @ts-ignore
  const viewport = window.visualViewport;
  const boundsRect = viewport
    ? new DOMRect(
        viewport.offsetLeft,
        viewport.offsetTop,
        viewport.width,
        viewport.height
      )
    : new DOMRect(0, 0, window.innerWidth, window.innerHeight);
  return boundsRect;
}

/**
 *
 * When a popup is first rendered, we let it render invisibly so that it doesn't
 * affect the page layout.
 *
 * We then wait, for two reasons:
 *
 * 1) We need to give the popup time to render invisibly. That lets us get the
 *    true size of the popup content.
 *
 * 2) Wire up events that can dismiss the popup. If the popup was opened because
 *    the user clicked something, that opening click event may still be bubbling
 *    up, and we only want to start listening after it's been processed.
 *    Along the same lines, if the popup caused the page to scroll, we don't
 *    want to immediately close because the page scrolled (only if the user
 *    scrolls).
 *
 * After waiting, we can take care of both of the above tasks.
 *
 * @private
 * @param {PopupSource} element
 */
function waitThenRenderOpened(element) {
  // Wait a tick to let the newly-opened component actually render.
  setTimeout(() => {
    // It's conceivable the popup was closed before the timeout completed,
    // so double-check that it's still opened before listening to events.
    if (element[state].opened) {
      choosePopupLayout(element);
      addEventListeners(element);
    }
  });
}

export default PopupSource;
<|endoftext|>