<commit_before>/*
 EnvJasmine: Jasmine test runner for EnvJS.

 EnvJasmine allows you to run headless JavaScript tests.

 Based on info from:
 http://agile.dzone.com/news/javascript-bdd-jasmine-without
 http://www.mozilla.org/rhino/
 http://www.envjs.com/
 http://pivotal.github.com/jasmine/
 https://github.com/velesin/jasmine-jquery
*/

importPackage(java.lang);
importPackage(java.io);
importPackage(org.mozilla.javascript);

var cx = Context.getCurrentContext();

cx.setOptimizationLevel(-1);

// Create the EnvJasmine namespace
if (!this.EnvJasmine) {
    this.EnvJasmine = {};
}

EnvJasmine.about = function () {
    print("Usage: envjasmine.js --disableColor --environment=<WIN|UNIX> --rootDir=<EnvJasmine Root Directory> [<js spec files>...]");
    System.exit(1);
};

EnvJasmine.normalizePath = function(path) {
    var endsInSlash = (path.slice(-1) == "/");

    if (path.slice(0, 1) == ".") {
        path = EnvJasmine.rootDir + "/" + path;
    }

    return File(path).getCanonicalPath() + (endsInSlash ? "/" : "");
};

EnvJasmine.load = (function() {
    var loaded = [];

    return function(path) {
        if (loaded.indexOf(path) == -1) {
            loaded.push(path);

            load(EnvJasmine.normalizePath(path));
        }
    };
}());

// Process command line options

(function(argumentList) {
    var arg, nameValue, spec = "";

    EnvJasmine.specs = [];
    EnvJasmine.passedCount = 0;
    EnvJasmine.failedCount = 0;
    EnvJasmine.totalCount = 0;

    for (var i = 0; i < argumentList.length; i++) {
        arg = argumentList[i];

        if (arg.slice(0, 2) == "--") {
            nameValue = arg.slice(2).split('=');

            switch(nameValue[0]) {
                case "rootDir":
                    EnvJasmine.rootDir = EnvJasmine.normalizePath(nameValue[1]);
                    break;
                case "environment":
                    EnvJasmine.environment = nameValue[1];
                    break;
                case "configFile":
                    EnvJasmine.configFile = EnvJasmine.normalizePath(nameValue[1]);
                    break;
                case "disableColor":
                    EnvJasmine.disableColor = true;
                    break;
                default:
                    print("Unknown option: " + arg);
                    break;
            }
        } else {
            if (arg.slice(-3) !== ".js") {
                spec += arg + " ";
            } else {
                spec += arg;
                EnvJasmine.specs.push(EnvJasmine.normalizePath(EnvJasmine.rootDir + "/" + spec));
                spec = "";
            }
        }
    }
}(arguments));

if (typeof EnvJasmine.rootDir == "undefined" || typeof EnvJasmine.environment == "undefined") {
    EnvJasmine.about();
}

EnvJasmine.SEPARATOR = (function (env) {
    if (env == "UNIX") {
        return "/";
    } else if  (env == "WIN") {
        return "\\";
    } else {
        EnvJasmine.about();
    }
}(EnvJasmine.environment));

EnvJasmine.disableColor = (function (env) {
    return EnvJasmine.disableColor || (env == "WIN");
}(EnvJasmine.environment));

if (EnvJasmine.disableColor) {
    EnvJasmine.green = "";
    EnvJasmine.red = "";
    EnvJasmine.endColor = "";
} else {
    EnvJasmine.green = "\033[32m";
    EnvJasmine.red = "\033[31m";
    EnvJasmine.endColor = "\033[0m";
}

// These are standard driectories in the EnvJasmine project.
EnvJasmine.libDir = EnvJasmine.rootDir + "/lib/";
EnvJasmine.includeDir = EnvJasmine.rootDir + "/include/";
EnvJasmine.specSuffix = new RegExp(/.spec.js$/);

// Load the envjasmine environment
EnvJasmine.loadEnv = function () {
    EnvJasmine.load(EnvJasmine.libDir + "envjs/env.rhino.1.2.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine/jasmine.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-ajax/mock-ajax.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-ajax/spec-helper.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-jquery/jasmine-jquery-1.2.0.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-rhino-reporter/jasmine-rhino-reporter.js");
};

// Load external dependencies
if (typeof EnvJasmine.configFile == "undefined") {
    EnvJasmine.configFile = EnvJasmine.includeDir + "dependencies.js";
}
EnvJasmine.loadConfig = function () {
    EnvJasmine.load(EnvJasmine.configFile);
};

if (typeof EnvJasmine.testDir == 'undefined') {
    EnvJasmine.testDir = EnvJasmine.rootDir;
}

if (typeof EnvJasmine.mocksDir == 'undefined') {
    EnvJasmine.mocksDir = EnvJasmine.testDir + "/mocks/";
}

if (typeof EnvJasmine.specsDir == 'undefined') {
    EnvJasmine.specsDir = EnvJasmine.testDir + "/specs/";
}

EnvJasmine.testDir = EnvJasmine.normalizePath(EnvJasmine.testDir);
EnvJasmine.mocksDir = EnvJasmine.normalizePath(EnvJasmine.mocksDir);
EnvJasmine.specsDir = EnvJasmine.normalizePath(EnvJasmine.specsDir);

(function() {
    var i, fileIn, len, scope;

    EnvJasmine.loadEnv();
    EnvJasmine.loadConfig();

    EnvJasmine.load(EnvJasmine.libDir + "spanDir/spanDir.js");
    if (EnvJasmine.specs.length == 0) {
        spanDir(EnvJasmine.specsDir, function(spec) {
            if (EnvJasmine.specSuffix.test(spec)) {
                EnvJasmine.specs.push(spec);
            }
        });
    }

    for (i = 0, len = EnvJasmine.specs.length >>> 0; i < len; i += 1) {
        print("Loading " + EnvJasmine.specs[i]);
        try {
            scope = {};
            fileIn = new FileReader(EnvJasmine.specs[i]);
            cx.evaluateReader(scope, fileIn, '<load>', 0, null);
            cx.evaluateString(scope, 'window.location.assign(["file://", EnvJasmine.libDir, "envjasmine.html"].join(EnvJasmine.SEPARATOR));', '<exec>', 0, null);
        }
        finally {
            fileIn.close();
        }
    }

    print("\nRESULTS:");
    print(EnvJasmine.green + "Final Passed: " + EnvJasmine.passedCount + EnvJasmine.endColor);
    print(EnvJasmine.red + "Final Failed: " + EnvJasmine.failedCount + EnvJasmine.endColor);
    print("Final Total: " + EnvJasmine.totalCount);
}());
<commit_msg>Added the ability to suppress console messages with the --supressConsoleMsgs option<commit_after>/*
 EnvJasmine: Jasmine test runner for EnvJS.

 EnvJasmine allows you to run headless JavaScript tests.

 Based on info from:
 http://agile.dzone.com/news/javascript-bdd-jasmine-without
 http://www.mozilla.org/rhino/
 http://www.envjs.com/
 http://pivotal.github.com/jasmine/
 https://github.com/velesin/jasmine-jquery
*/

importPackage(java.lang);
importPackage(java.io);
importPackage(org.mozilla.javascript);

var cx = Context.getCurrentContext();

cx.setOptimizationLevel(-1);

// Create the EnvJasmine namespace
if (!this.EnvJasmine) {
    this.EnvJasmine = {};
}

EnvJasmine.about = function () {
    print("Usage: envjasmine.js --suppressConsoleMsgs --disableColor --environment=<WIN|UNIX> --rootDir=<EnvJasmine Root Directory> [<js spec files>...]");
    System.exit(1);
};

EnvJasmine.normalizePath = function(path) {
    var endsInSlash = (path.slice(-1) == "/");

    if (path.slice(0, 1) == ".") {
        path = EnvJasmine.rootDir + "/" + path;
    }

    return File(path).getCanonicalPath() + (endsInSlash ? "/" : "");
};

EnvJasmine.load = (function() {
    var loaded = [];

    return function(path) {
        if (loaded.indexOf(path) == -1) {
            loaded.push(path);

            load(EnvJasmine.normalizePath(path));
        }
    };
}());

// Process command line options

(function(argumentList) {
    var arg, nameValue, spec = "";

    EnvJasmine.specs = [];
    EnvJasmine.passedCount = 0;
    EnvJasmine.failedCount = 0;
    EnvJasmine.totalCount = 0;

    for (var i = 0; i < argumentList.length; i++) {
        arg = argumentList[i];

        if (arg.slice(0, 2) == "--") {
            nameValue = arg.slice(2).split('=');

            switch(nameValue[0]) {
                case "rootDir":
                    EnvJasmine.rootDir = EnvJasmine.normalizePath(nameValue[1]);
                    break;
                case "environment":
                    EnvJasmine.environment = nameValue[1];
                    break;
                case "configFile":
                    EnvJasmine.configFile = EnvJasmine.normalizePath(nameValue[1]);
                    break;
                case "disableColor":
                    EnvJasmine.disableColor = true;
                    break;
                case "supressConsoleMsgs":
                    EnvJasmine.suppressConsoleMsgs = true;
                default:
                    print("Unknown option: " + arg);
                    break;
            }
        } else {
            if (arg.slice(-3) !== ".js") {
                spec += arg + " ";
            } else {
                spec += arg;
                EnvJasmine.specs.push(EnvJasmine.normalizePath(EnvJasmine.rootDir + "/" + spec));
                spec = "";
            }
        }
    }
}(arguments));

if (typeof EnvJasmine.rootDir == "undefined" || typeof EnvJasmine.environment == "undefined") {
    EnvJasmine.about();
}

EnvJasmine.SEPARATOR = (function (env) {
    if (env == "UNIX") {
        return "/";
    } else if  (env == "WIN") {
        return "\\";
    } else {
        EnvJasmine.about();
    }
}(EnvJasmine.environment));

EnvJasmine.disableColor = (function (env) {
    return EnvJasmine.disableColor || (env == "WIN");
}(EnvJasmine.environment));

if (EnvJasmine.disableColor) {
    EnvJasmine.green = "";
    EnvJasmine.red = "";
    EnvJasmine.endColor = "";
} else {
    EnvJasmine.green = "\033[32m";
    EnvJasmine.red = "\033[31m";
    EnvJasmine.endColor = "\033[0m";
}

// These are standard driectories in the EnvJasmine project.
EnvJasmine.libDir = EnvJasmine.rootDir + "/lib/";
EnvJasmine.includeDir = EnvJasmine.rootDir + "/include/";
EnvJasmine.specSuffix = new RegExp(/.spec.js$/);

// Load the envjasmine environment
EnvJasmine.loadEnv = function () {
    EnvJasmine.load(EnvJasmine.libDir + "envjs/env.rhino.1.2.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine/jasmine.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-ajax/mock-ajax.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-ajax/spec-helper.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-jquery/jasmine-jquery-1.2.0.js");
    EnvJasmine.load(EnvJasmine.libDir + "jasmine-rhino-reporter/jasmine-rhino-reporter.js");
};

// Load external dependencies
if (typeof EnvJasmine.configFile == "undefined") {
    EnvJasmine.configFile = EnvJasmine.includeDir + "dependencies.js";
}
EnvJasmine.loadConfig = function () {
    EnvJasmine.load(EnvJasmine.configFile);
};

if (typeof EnvJasmine.testDir == 'undefined') {
    EnvJasmine.testDir = EnvJasmine.rootDir;
}

if (typeof EnvJasmine.mocksDir == 'undefined') {
    EnvJasmine.mocksDir = EnvJasmine.testDir + "/mocks/";
}

if (typeof EnvJasmine.specsDir == 'undefined') {
    EnvJasmine.specsDir = EnvJasmine.testDir + "/specs/";
}

EnvJasmine.testDir = EnvJasmine.normalizePath(EnvJasmine.testDir);
EnvJasmine.mocksDir = EnvJasmine.normalizePath(EnvJasmine.mocksDir);
EnvJasmine.specsDir = EnvJasmine.normalizePath(EnvJasmine.specsDir);

(function() {
    var i, fileIn, len, scope;

    EnvJasmine.loadEnv();
    EnvJasmine.loadConfig();

    if (EnvJasmine.suppressConsoleMsgs === true) {
        // suppress console messages
        window.console = $.extend({}, window.console, {
            info: jasmine.createSpy(),
            log: jasmine.createSpy(),
            debug: jasmine.createSpy(),
            warning: jasmine.createSpy(),
            error: jasmine.createSpy()
        });
    }

    EnvJasmine.load(EnvJasmine.libDir + "spanDir/spanDir.js");
    if (EnvJasmine.specs.length == 0) {
        spanDir(EnvJasmine.specsDir, function(spec) {
            if (EnvJasmine.specSuffix.test(spec)) {
                EnvJasmine.specs.push(spec);
            }
        });
    }

    for (i = 0, len = EnvJasmine.specs.length >>> 0; i < len; i += 1) {
        print("Loading " + EnvJasmine.specs[i]);
        try {
            scope = {};
            fileIn = new FileReader(EnvJasmine.specs[i]);
            cx.evaluateReader(scope, fileIn, '<load>', 0, null);
            cx.evaluateString(scope, 'window.location.assign(["file://", EnvJasmine.libDir, "envjasmine.html"].join(EnvJasmine.SEPARATOR));', '<exec>', 0, null);
        }
        finally {
            fileIn.close();
        }
    }

    print("\nRESULTS:");
    print(EnvJasmine.green + "Final Passed: " + EnvJasmine.passedCount + EnvJasmine.endColor);
    print(EnvJasmine.red + "Final Failed: " + EnvJasmine.failedCount + EnvJasmine.endColor);
    print("Final Total: " + EnvJasmine.totalCount);
}());
<|endoftext|>


<commit_before>var os    = require('os');
var async = require('async');
var NR    = require('node-resque');

var resque = function(api, next){

  api.resque = {
    queue: null,
    workers: [],
    scheduler: null,
    connectionDetails: api.config.tasks.redis || {},

    _start: function(api, next){
      var self = this;
      self.startQueue(function(){
        self.startScheduler(function(){
          self.startWorkers(function(){
            next();
          });
        });
      });
    },

    _stop: function(api, next){
      var self = this;
      self.stopScheduler(function(){
        self.stopWorkers(function(){
          self.queue.end(function(){
            next();
          });
        });
      });
    },

    startQueue: function(callback){
      var self = this;
      self.queue = new NR.queue({connection: self.connectionDetails}, api.tasks.jobs, function(){
        callback();
      });
    },

    startScheduler: function(callback){
      var self = this;
      if(api.config.tasks.scheduler === true){
        self.scheduler = new NR.scheduler({connection: self.connectionDetails, timeout: api.config.tasks.timeout}, function(){
          self.scheduler.on('start',             function(){               api.log('resque scheduler started', 'info') })
          self.scheduler.on('end',               function(){               api.log('resque scheduler ended', 'info') })
          self.scheduler.on('poll',              function(){               api.log('resque scheduler polling', 'debug') })
          self.scheduler.on('working_timestamp', function(timestamp){      api.log('resque scheduler working timestamp ' + timestamp, 'debug') })
          self.scheduler.on('transferred_job',   function(timestamp, job){ api.log('resque scheduler enqueuing job ' + timestamp, 'debug', job) })

          self.scheduler.start();

          process.nextTick(function(){
            callback();
          });
        });
      } else {
        callback();
      }
    },

    stopScheduler: function(callback){
      var self = this;
      if(!self.scheduler){
        callback();
      } else {
        self.scheduler.end(function(){
          delete self.scheduler;
          callback();
        });
      }
    },

    startWorkers: function(callback){
      var self = this;
      var i = 0;
      if(!api.config.tasks.queues || api.config.tasks.queues.length === 0){
        callback();
      } else {
        var starters = [];
        while(i < api.config.tasks.queues.length){
          starters.push( async.apply(self.startWorker, i) )
          i++;
        }
        async.parallel(starters, callback);
      }
    },

    startWorker: function(counter, callback){
      var timeout = api.config.tasks.timeout;
      var name = os.hostname() + ':' + process.pid + '+' + (counter+1);
      // var name = os.hostname() + ':' + process.pid;
      var worker = new NR.worker({
        connection: api.resque.connectionDetails,
        name: name,
        queues: api.config.tasks.queues[counter],
        timeout: timeout
      }, api.tasks.jobs, function(){
        worker.on('start',           function(){                   api.log('resque worker #'+(counter+1)+' started (queues: ' + worker.options.queues + ')', 'info'); })
        worker.on('end',             function(){                   api.log('resque worker #'+(counter+1)+' ended', 'info'); })
        worker.on('cleaning_worker', function(worker, pid){        api.log('resque cleaning old worker ' + worker + '(' + pid + ')', 'info'); })
        worker.on('poll',            function(queue){              api.log('resque worker #'+(counter+1)+' polling ' + queue, 'debug'); })
        worker.on('job',             function(queue, job){         api.log('resque worker #'+(counter+1)+' working job ' + queue, 'debug', job); })
        worker.on('success',         function(queue, job, result){ api.log('resque worker #'+(counter+1)+' job success ' + queue, 'info', {job: job, result: result}); })
        worker.on('pause',           function(){                   api.log('resque worker #'+(counter+1)+'  paused', 'debug'); })
        worker.on('failure',         function(queue, job, f){ api.exceptionHandlers.task(f, queue, job) })
        worker.on('error',           function(queue, job, error){ api.exceptionHandlers.task(error, queue, job) })

        worker.workerCleanup();
        worker.start();
        api.resque.workers[counter] = worker;

        callback();
      });
    },

    stopWorkers: function(callback){
      var self = this;
      if(self.workers.length === 0){
        callback();
      } else {
        var ended = 0;
        self.workers.forEach(function(worker){
          api.log('stopping worker: ' + worker.name, 'debug');
          worker.end(function(){
            ended++;
            if(ended === self.workers.length){
              self.workers = [];
              callback();
            }
          });
        });
      }
    }
  }

  if(api.resque.connectionDetails.fake == true){
    api.resque.connectionDetails.package = require('fakeredis');
  }

  next();

}

exports.resque = resque;
<commit_msg>change job logger<commit_after>var os    = require('os');
var async = require('async');
var NR    = require('node-resque');

var resque = function(api, next){

  api.resque = {
    queue: null,
    workers: [],
    scheduler: null,
    connectionDetails: api.config.tasks.redis || {},

    _start: function(api, next){
      var self = this;
      self.startQueue(function(){
        self.startScheduler(function(){
          self.startWorkers(function(){
            next();
          });
        });
      });
    },

    _stop: function(api, next){
      var self = this;
      self.stopScheduler(function(){
        self.stopWorkers(function(){
          self.queue.end(function(){
            next();
          });
        });
      });
    },

    startQueue: function(callback){
      var self = this;
      self.queue = new NR.queue({connection: self.connectionDetails}, api.tasks.jobs, function(){
        callback();
      });
    },

    startScheduler: function(callback){
      var self = this;
      if(api.config.tasks.scheduler === true){
        self.scheduler = new NR.scheduler({connection: self.connectionDetails, timeout: api.config.tasks.timeout}, function(){
          self.scheduler.on('start',             function(){               api.log('resque scheduler started', 'info') })
          self.scheduler.on('end',               function(){               api.log('resque scheduler ended', 'info') })
          self.scheduler.on('poll',              function(){               api.log('resque scheduler polling', 'debug') })
          self.scheduler.on('working_timestamp', function(timestamp){      api.log('resque scheduler working timestamp ' + timestamp, 'debug') })
          self.scheduler.on('transferred_job',   function(timestamp, job){ api.log('resque scheduler enqueuing job ' + timestamp, 'debug', job) })

          self.scheduler.start();

          process.nextTick(function(){
            callback();
          });
        });
      } else {
        callback();
      }
    },

    stopScheduler: function(callback){
      var self = this;
      if(!self.scheduler){
        callback();
      } else {
        self.scheduler.end(function(){
          delete self.scheduler;
          callback();
        });
      }
    },

    startWorkers: function(callback){
      var self = this;
      var i = 0;
      if(!api.config.tasks.queues || api.config.tasks.queues.length === 0){
        callback();
      } else {
        var starters = [];
        while(i < api.config.tasks.queues.length){
          starters.push( async.apply(self.startWorker, i) )
          i++;
        }
        async.parallel(starters, callback);
      }
    },

    startWorker: function(counter, callback){
      var timeout = api.config.tasks.timeout;
      var name = os.hostname() + ':' + process.pid + '+' + (counter+1);
      // var name = os.hostname() + ':' + process.pid;
      var worker = new NR.worker({
        connection: api.resque.connectionDetails,
        name: name,
        queues: api.config.tasks.queues[counter],
        timeout: timeout
      }, api.tasks.jobs, function(){
        worker.on('start',           function(){                   api.log('resque worker #'+(counter+1)+' started (queues: ' + worker.options.queues + ')', 'info'); })
        worker.on('end',             function(){                   api.log('resque worker #'+(counter+1)+' ended', 'info'); })
        worker.on('cleaning_worker', function(worker, pid){        api.log('resque cleaning old worker ' + worker + '(' + pid + ')', 'info'); })
        worker.on('poll',            function(queue){              api.log('resque worker #'+(counter+1)+' polling ' + queue, 'debug'); })
        worker.on('job',             function(queue, job){         api.log('resque worker #'+(counter+1)+' working job ' + queue, 'debug', {job: {class: job.class, queue: job.queue}}); })
        worker.on('success',         function(queue, job, result){ api.log('resque worker #'+(counter+1)+' job success ' + queue, 'info', {job: {class: job.class, queue: job.queue}, result: result}); })
        worker.on('pause',           function(){                   api.log('resque worker #'+(counter+1)+'  paused', 'debug'); })
        worker.on('failure',         function(queue, job, f){ api.exceptionHandlers.task(f, queue, job) })
        worker.on('error',           function(queue, job, error){ api.exceptionHandlers.task(error, queue, job) })

        worker.workerCleanup();
        worker.start();
        api.resque.workers[counter] = worker;

        callback();
      });
    },

    stopWorkers: function(callback){
      var self = this;
      if(self.workers.length === 0){
        callback();
      } else {
        var ended = 0;
        self.workers.forEach(function(worker){
          api.log('stopping worker: ' + worker.name, 'debug');
          worker.end(function(){
            ended++;
            if(ended === self.workers.length){
              self.workers = [];
              callback();
            }
          });
        });
      }
    }
  }

  if(api.resque.connectionDetails.fake == true){
    api.resque.connectionDetails.package = require('fakeredis');
  }

  next();

}

exports.resque = resque;
<|endoftext|>


<commit_before><commit_msg>Updating minified version<commit_after><|endoftext|>


<commit_before>'use strict';

var Game = require( './game' );
var Object3D = require( './object3d' );
var Geometry = require( './geometry/geometry' );
var Mesh = require( './objects/mesh' );
var Color = require( './math/color' );
var DirectionalLight = require( './lights/directional-light' );
var LambertMaterial = require( './materials/lambert-material' );
var addBoxGeometry = require( './geometry/box-geometry' );
var createPlaneGeometry = require( './geometry/plane-geometry' );
var OrbitControls = require( './controls/orbit-controls' );

var $ = document.querySelector.bind( document );

var WIDTH = 852;
var HEIGHT = 480;

function on( el, type, listener ) {
  el.addEventListener( type, listener );
}

function append( parent, el ) {
  parent.appendChild( el );
}

var game = new Game(
  Math.min( window.innerWidth,  WIDTH  ),
  Math.min( window.innerHeight, HEIGHT )
);

var container = $( '#g' );
append( container, game.canvas );

var scene;

function reset() {
  scene = game.scene = new Object3D();

  var buildings = new Geometry();
  addBoxGeometry( buildings, 1, 2.5, 1 );
  buildings.computeFaceNormals();

  var material = new LambertMaterial({
    color: new Color( 0.8, 0.8, 0.8 ),
    ambient: new Color( 0.5, 0.5, 0.5 ),
    diffuse: new Color( 0.5, 0.5, 0.5 ),
    overdraw: 0.5
  });

  var mesh = new Mesh( buildings, material );
  scene.add( mesh );

  var planeGeometry = createPlaneGeometry( 16, 16, 16, 16 );
  planeGeometry.vertices.forEach(function( vertex ) {
    vertex.y = Math.random();
  });
  planeGeometry.computeFaceNormals();

  var planeMaterial = new LambertMaterial({
    color: new Color( 0.5, 0.5, 0.5 ),
    ambient: new Color( 0.2, 0.2, 0.2 ),
    diffuse: new Color( 0.5, 0.5, 0.5 ),
    overdraw: 0.5
  });
  var planeMesh = new Mesh( planeGeometry, planeMaterial );

  scene.add( planeMesh );

  var light = new DirectionalLight( new Color( 1, 0.8, 0.8 ) );
  light.position.set( -4, 4, 5 );
  scene.add( light );

  game.ambient.setRGB( 0.2, 0.2, 0.2 );

  game.camera.position.set( -2, 5, -2 );
  game.camera.lookAt( mesh.position );
  game.camera.updateProjectionMatrix();

  new OrbitControls( game.camera );
}

reset();
game.play();

on( window, 'resize', function() {
  game.setSize( window.innerWidth, window.innerHeight );
});
<commit_msg>Add cylinder geometry example mesh.<commit_after>'use strict';

var Game = require( './game' );
var Object3D = require( './object3d' );
var Geometry = require( './geometry/geometry' );
var Mesh = require( './objects/mesh' );
var Color = require( './math/color' );
var DirectionalLight = require( './lights/directional-light' );
var LambertMaterial = require( './materials/lambert-material' );
var addBoxGeometry = require( './geometry/box-geometry' );
var createCylinderGeometry = require( './geometry/cylinder-geometry' );
var createPlaneGeometry = require( './geometry/plane-geometry' );
var OrbitControls = require( './controls/orbit-controls' );

var $ = document.querySelector.bind( document );

var WIDTH = 852;
var HEIGHT = 480;

function on( el, type, listener ) {
  el.addEventListener( type, listener );
}

function append( parent, el ) {
  parent.appendChild( el );
}

var game = new Game(
  Math.min( window.innerWidth,  WIDTH  ),
  Math.min( window.innerHeight, HEIGHT )
);

var container = $( '#g' );
append( container, game.canvas );

var scene;

function reset() {
  scene = game.scene = new Object3D();

  var buildings = new Geometry();
  addBoxGeometry( buildings, 1, 2.5, 1 );
  buildings.computeFaceNormals();

  var material = new LambertMaterial({
    color: new Color( 0.8, 0.8, 0.8 ),
    ambient: new Color( 0.5, 0.5, 0.5 ),
    diffuse: new Color( 0.5, 0.5, 0.5 ),
    overdraw: 0.5
  });

  var mesh = new Mesh( buildings, material );
  scene.add( mesh );

  var cylinder = createCylinderGeometry( 0, 1, 2.5, 4, 1 );
  var cylinderMesh = new Mesh( cylinder, material );
  cylinderMesh.position.x = 2;
  cylinderMesh.position.y = 1.25;
  scene.add( cylinderMesh );

  var planeGeometry = createPlaneGeometry( 16, 16, 16, 16 );
  planeGeometry.vertices.map(function( vertex ) {
    vertex.y = Math.random();
  });
  planeGeometry.computeFaceNormals();

  var planeMaterial = new LambertMaterial({
    color: new Color( 0.5, 0.5, 0.5 ),
    ambient: new Color( 0.2, 0.2, 0.2 ),
    diffuse: new Color( 0.5, 0.5, 0.5 ),
    overdraw: 0.5
  });
  var planeMesh = new Mesh( planeGeometry, planeMaterial );

  scene.add( planeMesh );

  var light = new DirectionalLight( new Color( 1, 0.8, 0.8 ) );
  light.position.set( -4, 4, 5 );
  scene.add( light );

  game.ambient.setRGB( 0.2, 0.2, 0.2 );

  game.camera.position.set( -2, 5, -2 );
  game.camera.lookAt( mesh.position );
  game.camera.updateProjectionMatrix();

  new OrbitControls( game.camera );
}

reset();
game.play();

on( window, 'resize', function() {
  game.setSize( window.innerWidth, window.innerHeight );
});
<|endoftext|>


<commit_before>/**
 * @package      SD-NodeChat
 *
 * @author       Saurin Dashadia (SD) (http://saur.in)
 * @copyright    Copyright (C) 2014 Saurin Dashadia. All rights reserved.
 * @license      http://opensource.org/licenses/MIT, see LICENSE
 */

(function (window, document, $) {

    $.SDNodeChat = function (options) {

        var _defaults = {

            /**
             * @default     localhost
             * @type        Text
             * @value       server-IP address
             * @usage       This will be consider as default server for chat
             */
            server: "localhost",

            /**
             * @default     80
             * @type        Text
             * @value       Port number
             * @usage       This will be consider as default port for the chat server
             */
            port: "80",

            /**
             * @default     null
             * @type        [object Socket]
             * @value       [object Socket]
             * @usage       This socket object will be use to communicate to server.
             */
            socket: null,

            /**
             * @default     false
             * @type        boolean
             * @value       true/false
             * @usage       This can enable or disable log on server side.
             */
            debug: false,

            /**
             * @default     null
             * @type        Text
             * @value       username
             * @usage       This can enable or disable log on server side.
             */
            username: null,

            /**
             * @default     $('#messages')
             * @type        [object HTMLUListElement]
             * @value       [object HTMLUListElement]
             * @usage       This element will be consider as chat container
             */
            chatContainer: $('#messages'),

            /**
             * @default     $('#SendButtonID')
             * @type        [object HTMLButtonElement]
             * @value       [object HTMLButtonElement]
             * @usage       This element will be consider as an action to send chat
             */
            sendButton: $('#SendButtonID'),

            /**
             * @default     $('#chatTextBoxId')
             * @type        [object HTMLInputElement]
             * @value       [object HTMLInputElement]
             * @usage       This element will be consider as chat input it can also handle action to send chat
             */
            chatBox: $('#chatTextBox'),

            /**
             * @default     true
             * @type        boolean
             * @value       true/false
             * @usage       This can enable or disable enter key as sender on chatBox
             */
            enterToSend: true,

            /**
             * @default     $('#userContainer')
             * @type        [object HTMLUListElement]
             * @value       [object HTMLUListElement]
             * @usage       This element will be consider as a container of online users list.
             */
            usersContainer: $('#userContainer'),

            /**
             * @default     "Welcome to SD-NodeChat! For more info visit SD-NodeChat@GitHub"
             * @type        [text]
             * @value       [text]
             * @usage       This is to welcome a new user. It will be ignored if value not present.
             */
            welcomeNote: "Welcome to SD-NodeChat! For more info visit SD-NodeChat@GitHub",
        }

        // extend default vaules
        var SDNodeChatConf = $.extend(_defaults, options);

        // remove trailing slash from URL using regular expression, if there any.
        SDNodeChatConf.server = SDNodeChatConf.server.replace(/\/$/, '');

        return {
            /**
             *
             * @param       message {text}
             * @param       type    {text} ('self'/'other')
             * @returns     void
             * @usage       This function is use to display message.
             *              There are two ways to display messages
             *              1) type = 'self'    -> message from self
             *              2) type = 'other'   -> message from other users
             *              3) type = 'note'    -> Common notes to chat
             */
            addMsg : function (message, type) {
                var li = '
' + message + '
';
                $(SDNodeChatConf.chatContainer).append($(li));
                // scroll to new content
                this.scroll();
            },

            /**
             * @param       void
             * @returns     void
             * @usage       This function will be called when user send message. This function then add message
             *              to message container and will push message to server.
             */
            sendMsg: function () {

                var message = SDNodeChatConf.chatBox.val().trim();
                if (!message) return false;

                this.addMsg(message, 'self');
                this.pushMsg(message);

                SDNodeChatConf.chatBox.val('');
                SDNodeChatConf.chatBox.focus();
            },

            /**
             * @param       message {Text}
             * @param       socket  {object}
             * @returns     void
             * @usage       This function will push a user message to server
             */
            pushMsg: function (message) {
                SDNodeChatConf.socket.emit('newMsg', { message: message, username: SDNodeChatConf.username });
            },

            init : function(success, fail){
                var that = this;
                var _sdnc = SDNodeChatConf;

                var hostport = [];
                hostport.push( ((_sdnc.server)?_sdnc.server:null) );
                hostport.push( ((_sdnc.server && _sdnc.port)?_sdnc.port:null) );

                // initiate socket object
                _sdnc.socket = io(hostport.join(':'));

                //ask server to add this user.
                _sdnc.socket.emit('addUser', {username: _sdnc.username});
                _sdnc.socket.on('userAdded', function(data){
                    if(data.success === true){
                        // Add welcome note
                        if(_sdnc.welcomeNote){
                            that.addMsg(_sdnc.welcomeNote,'note');
                        }

                        // listen for new user joins
                        _sdnc.socket.on('newUser', function (data) {
                            that.addMsg(data.username + ' has joined chat.', 'note');
                        });

                        // listen for user left
                        _sdnc.socket.on('userLeft', function (data) {
                            that.addMsg(data.username + ' has left.', 'note');
                        });

                        // listen for new messages over socket
                        _sdnc.socket.on('newMsg', function (data) {
                            that.addMsg(data.username + ': ' +data.message, 'other');
                        });

                        // Bind click event to sendButton
                        _sdnc.sendButton.click(function () {
                            that.sendMsg();
                        });

                        // Bind chatbox to enter press if config set to truesendMessage
                        if (_sdnc.enterToSend === true) {
                            _sdnc.chatBox.bind('keypress', function (e) {
                                // check if key is enter key
                                if (e.keyCode == 13) {
                                    that.sendMsg();
                                }
                            });
                        }

                        // Set focus to chatbox
                        _sdnc.chatBox.focus();

                        // callback
                        success();
                    }else{
                        // callback
                        fail();
                    }
                });
            },

            /**
             * @param       SDNodeChatConf key
             * @returns     SDNodeChatConf value
             * @usage       This function is to get config value.
             */
            get: function(key){
                return SDNodeChatConf[key];
            },

            /**
             * @param       void
             * @returns     void
             * @usage       This function scrolls chat container to the new added message (bottom)
             */
            scroll:function(){
                jQuery(document).scrollTop(jQuery(document).innerHeight());
            },

            /**
             * @param       void
             * @returns     string
             * @usage       This function is to query clients basic info usually a name which can be use to identify
             *              user in chat room
             */
            logMessage: function(msg,isDebug){
                // Follow global debug flag if local flag not set.
                if(typeof isDebug === 'undefined') isDebug = SDNodeChatConf.debug;
                if(isDebug) console.log(msg);
            },

            /**
             * @param       void
             * @returns     void
             * @usage       This function is use to request list of online users
             */
            getUserList: function(){
                var that = this;
                SDNodeChatConf.socket.emit('userList');
                SDNodeChatConf.socket.on('userList', function (data) {
                    that.showUserList(data);
                });
            },

            /**
             * @param       data    {object}
             * @returns     void
             * @usage       This function is use to set friends list
             */
            showUserList:function(data){
                SDNodeChatConf.usersContainer.find('li').not('.caption').remove();
                var userList = JSON.parse(data);
                var userListHTML = '';
                for(u in userList){
                    userListHTML += '
' + userList[u] + '
';
                }
                SDNodeChatConf.usersContainer.append($(userListHTML));
            }
        };
    };

}(window, document, jQuery));<commit_msg>Regular expression pattern added for URL. formatLink() function added to identify URL inside message body and make them clickable.<commit_after>/**
 * @package      SD-NodeChat
 *
 * @author       Saurin Dashadia (SD) (http://saur.in)
 * @copyright    Copyright (C) 2014 Saurin Dashadia. All rights reserved.
 * @license      http://opensource.org/licenses/MIT, see LICENSE
 */

(function (window, document, $) {

    $.SDNodeChat = function (options) {

        var _defaults = {

            /**
             * @default     localhost
             * @type        Text
             * @value       server-IP address
             * @usage       This will be consider as default server for chat
             */
            server: "localhost",

            /**
             * @default     80
             * @type        Text
             * @value       Port number
             * @usage       This will be consider as default port for the chat server
             */
            port: "80",

            /**
             * @default     null
             * @type        [object Socket]
             * @value       [object Socket]
             * @usage       This socket object will be use to communicate to server.
             */
            socket: null,

            /**
             * @default     false
             * @type        boolean
             * @value       true/false
             * @usage       This can enable or disable log on server side.
             */
            debug: false,

            /**
             * @default     null
             * @type        Text
             * @value       username
             * @usage       This can enable or disable log on server side.
             */
            username: null,

            /**
             * @default     $('#messages')
             * @type        [object HTMLUListElement]
             * @value       [object HTMLUListElement]
             * @usage       This element will be consider as chat container
             */
            chatContainer: $('#messages'),

            /**
             * @default     $('#SendButtonID')
             * @type        [object HTMLButtonElement]
             * @value       [object HTMLButtonElement]
             * @usage       This element will be consider as an action to send chat
             */
            sendButton: $('#SendButtonID'),

            /**
             * @default     $('#chatTextBoxId')
             * @type        [object HTMLInputElement]
             * @value       [object HTMLInputElement]
             * @usage       This element will be consider as chat input it can also handle action to send chat
             */
            chatBox: $('#chatTextBox'),

            /**
             * @default     true
             * @type        boolean
             * @value       true/false
             * @usage       This can enable or disable enter key as sender on chatBox
             */
            enterToSend: true,

            /**
             * @default     $('#userContainer')
             * @type        [object HTMLUListElement]
             * @value       [object HTMLUListElement]
             * @usage       This element will be consider as a container of online users list.
             */
            usersContainer: $('#userContainer'),

            /**
             * @default     "Welcome to SD-NodeChat! For more info visit SD-NodeChat@GitHub"
             * @type        [text]
             * @value       [text]
             * @usage       This is to welcome a new user. It will be ignored if value not present.
             */
            welcomeNote: "Welcome to SD-NodeChat! For more info visit SD-NodeChat@GitHub",
        }

        // extend default vaules
        var SDNodeChatConf = $.extend(_defaults, options);

        // remove trailing slash from URL using regular expression, if there any.
        SDNodeChatConf.server = SDNodeChatConf.server.replace(/\/$/, '');

        // Regualr expression for URL
        SDNodeChatConf.pattern = {};
        SDNodeChatConf.pattern.URL = /(https|http)(:\/\/)([\w\d.-]+)(:[\d]+)?([\/\w\d._-]+)?(\?[\w\d=&-_+!@%\+]+)?(#[\w\d!=%\+]+)?/ig;
        SDNodeChatConf.pattern.URLPlaceholder = '$1$2$3$4$5$6$7';

        return {
            /**
             *
             * @param       message {text}
             * @param       type    {text} ('self'/'other')
             * @returns     void
             * @usage       This function is use to display message.
             *              There are two ways to display messages
             *              1) type = 'self'    -> message from self
             *              2) type = 'other'   -> message from other users
             *              3) type = 'note'    -> Common notes to chat
             */
            addMsg : function (message, type) {
                var li = '
' + message + '
';
                $(SDNodeChatConf.chatContainer).append($(li));
                // scroll to new content
                this.scroll();
            },

            /**
             * @param       void
             * @returns     void
             * @usage       This function will be called when user send message. This function then add message
             *              to message container and will push message to server.
             */
            sendMsg: function () {

                var message = SDNodeChatConf.chatBox.val().trim();
                if (!message) return false;

                message = this.formatLink(message);

                this.addMsg(message, 'self');
                this.pushMsg(message);

                SDNodeChatConf.chatBox.val('');
                SDNodeChatConf.chatBox.focus();
            },

            /**
             * @param       message {Text}
             * @returns     message {Text}
             * @usage       This function will push a user message to server
             */
            formatLink: function (message) {
                return message.replace(SDNodeChatConf.pattern.URL, '' + SDNodeChatConf.pattern.URLPlaceholder + '');
            },

            /**
             * @param       message {Text}
             * @returns     void
             * @usage       This function will push a user message to server
             */
            pushMsg: function (message) {
                SDNodeChatConf.socket.emit('newMsg', { message: message, username: SDNodeChatConf.username });
            },

            init : function(success, fail){
                var that = this;
                var _sdnc = SDNodeChatConf;

                var hostport = [];
                hostport.push( ((_sdnc.server)?_sdnc.server:null) );
                hostport.push( ((_sdnc.server && _sdnc.port)?_sdnc.port:null) );

                // initiate socket object
                _sdnc.socket = io(hostport.join(':'));

                //ask server to add this user.
                _sdnc.socket.emit('addUser', {username: _sdnc.username});
                _sdnc.socket.on('userAdded', function(data){
                    if(data.success === true){
                        // Add welcome note
                        if(_sdnc.welcomeNote){
                            that.addMsg(_sdnc.welcomeNote,'note');
                        }

                        // listen for new user joins
                        _sdnc.socket.on('newUser', function (data) {
                            that.addMsg(data.username + ' has joined chat.', 'note');
                        });

                        // listen for user left
                        _sdnc.socket.on('userLeft', function (data) {
                            that.addMsg(data.username + ' has left.', 'note');
                        });

                        // listen for new messages over socket
                        _sdnc.socket.on('newMsg', function (data) {
                            that.addMsg(data.username + ': ' +data.message, 'other');
                        });

                        // Bind click event to sendButton
                        _sdnc.sendButton.click(function () {
                            that.sendMsg();
                        });

                        // Bind chatbox to enter press if config set to truesendMessage
                        if (_sdnc.enterToSend === true) {
                            _sdnc.chatBox.bind('keypress', function (e) {
                                // check if key is enter key
                                if (e.keyCode == 13) {
                                    that.sendMsg();
                                }
                            });
                        }

                        // Set focus to chatbox
                        _sdnc.chatBox.focus();

                        // callback
                        success();
                    }else{
                        // callback
                        fail();
                    }
                });
            },

            /**
             * @param       SDNodeChatConf key
             * @returns     SDNodeChatConf value
             * @usage       This function is to get config value.
             */
            get: function(key){
                return SDNodeChatConf[key];
            },

            /**
             * @param       void
             * @returns     void
             * @usage       This function scrolls chat container to the new added message (bottom)
             */
            scroll:function(){
                jQuery(document).scrollTop(jQuery(document).innerHeight());
            },

            /**
             * @param       void
             * @returns     string
             * @usage       This function is to query clients basic info usually a name which can be use to identify
             *              user in chat room
             */
            logMessage: function(msg,isDebug){
                // Follow global debug flag if local flag not set.
                if(typeof isDebug === 'undefined') isDebug = SDNodeChatConf.debug;
                if(isDebug) console.log(msg);
            },

            /**
             * @param       void
             * @returns     void
             * @usage       This function is use to request list of online users
             */
            getUserList: function(){
                var that = this;
                SDNodeChatConf.socket.emit('userList');
                SDNodeChatConf.socket.on('userList', function (data) {
                    that.showUserList(data);
                });
            },

            /**
             * @param       data    {object}
             * @returns     void
             * @usage       This function is use to set friends list
             */
            showUserList:function(data){
                SDNodeChatConf.usersContainer.find('li').not('.caption').remove();
                var userList = JSON.parse(data);
                var userListHTML = '';
                for(u in userList){
                    userListHTML += '
' + userList[u] + '
';
                }
                SDNodeChatConf.usersContainer.append($(userListHTML));
            }
        };
    };

}(window, document, jQuery));<|endoftext|>


<commit_before><commit_msg>--typo<commit_after><|endoftext|>


<commit_before>var glob = require('glob');

var globArray = function(patterns, options, cb) {
  patterns = patterns || [];
  options = options || {};

  patterns.forEach(function(pattern) {
    var patternMatches = glob(pattern, options, cb);
  });
};

globArray.sync = function(patterns, options) {
  var fileMatches = [];
  patterns = patterns || [];
  options = options || {};

  patterns.forEach(function(pattern) {
    var patternMatches = glob.sync(pattern, options);

    fileMatches = fileMatches.concat(patternMatches);
  });

  return fileMatches;
};

module.exports = globArray;
<commit_msg>removed async version (till I can figure out how to get it working properly with negative filters) added ability to do negative filters using ! in front<commit_after>var glob = require('glob');
var filterNegative = function(negativeMatches, allMatches) {
  negativeMatches.forEach(function(match) {
    var index = allMatches.indexOf(match);

    if(index > -1) {
      allMatches.splice(index, 1);
    }
  })
};

globArray.sync = function(patterns, options) {
  var fileMatches = [];
  patterns = patterns || [];
  options = options || {};

  patterns.forEach(function(pattern) {
    var patternMatches = glob.sync(pattern, options);

    if(pattern.substr(0, 1) !== '!') {
      fileMatches = fileMatches.concat(patternMatches);
    } else {
      filterNegative(patternMatches, fileMatches);
    }
  });

  return fileMatches;
};

module.exports = globArray;
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var request = require('supertest')('http://localhost:5000');
var db = require('../database');
var users = db.get('users');
var posts = db.get('posts');

describe('API', function () {
  var testuser;
  var otheruser;
  before(function () {
    users.findOne({apiKey: 'thetestuserapikey'}, function (err, doc) {
      testuser = doc;
    });
    users.findOne({apiKey: 'theotheruserapikey'}, function (err, doc) {
      otheruser = doc;
    });
  });

  describe('Authentication', function () {
    it('should allow access without api key', function (done) {
      request.get('/').expect(200, done);
    });
    it('should not allow access with an invalid api key', function (done) {
      request.get('/?api_key=notavalidapikey').expect(403, done);
    });
    it('should allow access with api key via GET parameter api_key', function (done) {
      request.get('/?api_key=thetestuserapikey').expect(200, done);
    });
    it('should allow access with api key via GET parameter apiKey', function (done) {
      request.get('/?apiKey=thetestuserapikey').expect(200, done);
    });
    it('should redirect /auth/google to Google oAuth2', function (done) {
      request.get('/auth/google').expect(302, function (err, res) {
        expect(res.header['location']).to.match(/^https:\/\/accounts.google.com\/o\/oauth2\/auth/);
        done(err);
      });
    });
    it('should redirect /auth/facebook to Facebook oAuth2', function (done) {
      request.get('/auth/facebook').expect(302, function (err, res) {
        expect(res.header['location']).to.match(/^https:\/\/www.facebook.com\/dialog\/oauth/);
        done(err);
      });
    });
  });

  describe('/posts', function () {
    describe('HTTP GET', function () {
      describe('without API key', function () {
        it('should return 200 OK', function (done) {
          request.get('/posts/').expect(200, done);
        });
        it('should not return more than 25 posts', function (done) {
          request.get('/posts/').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(26);
            done(err);
          });
        });
        it('should not return more than 10 posts if parameter "limit=10" is passed', function (done) {
          request.get('/posts/?limit=10').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(11);
            done(err);
          });
        });
        it('should return 400 BAD REQUEST if parameter "limit" is above 25', function (done) {
          request.get('/posts/?limit=26').expect(400, done);
        });
        it('should return 400 BAD REQUEST if parameter "limit" is below 1', function (done) {
          request.get('/posts/?limit=0').expect(400, done);
        });
      });
      describe('with API key', function () {
        it('should return 200 OK', function (done) {
          request.get('/posts/?apiKey=thetestuserapikey').expect(200, done);
        });
        it('should not return more than 25 posts', function (done) {
          request.get('/posts/?apiKey=thetestuserapikey').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(26);
            done(err);
          });
        });
        it('should not return more than 10 posts if parameter "limit=10" is passed', function (done) {
          request.get('/posts/?limit=10&apiKey=thetestuserapikey').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(11);
            done(err);
          });
        });
        it('should return 400 BAD REQUEST if parameter "limit" is above 25', function (done) {
          request.get('/posts/?limit=26&apiKey=thetestuserapikey').expect(400, done);
        });
        it('should return 400 BAD REQUEST if parameter "limit" is below 1', function (done) {
          request.get('/posts/?limit=0&apiKey=thetestuserapikey').expect(400, done);
        });
      });
    });
    describe('HTTP POST', function () {
      describe('without API key', function () {
        it('should return 403 FORBIDDEN', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN with missing parameter "lat"', function (done) {
          request.post('/posts/').send({
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN with missing parameter "long"', function (done) {
          request.post('/posts/').send({
            lat: 13,
            message: 'Testmessage'
          }).expect(403, done)
        });
        it('should return 403 FORBIDDEN with missing parameter "message"', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: 37
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "lat" is above 90', function (done) {
          request.post('/posts/').send({
            lat: 90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "lat" is below -90', function (done) {
          request.post('/posts/').send({
            lat: -90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "long" is above 180', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: 180.1,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "long" is below -180', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: -180.1,
            message: 'Testmessage'
          }).expect(403, done);
        });
      });
      describe('with API key', function () {
        it('should return 200 OK and return the created post', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: 37,
            message: 'Testmessage'
          }).expect(200, function (err, res) {
            var post = res.body;
            expect(post.user).to.equal(testuser._id.toString());
            expect(post.lat).to.equal(13);
            expect(post.long).to.equal(37);
            expect(post.message).to.equal('Testmessage');
            expect(post._id).to.exist;
            done(err);
          });
        });
        it('should return 400 BAD REQUEST with missing parameter "lat"', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            long: 37,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with missing parameter "long"', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            message: 'Testmessage'
          }).expect(400, done)
        });
        it('should return 400 BAD REQUEST with missing parameter "message"', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: 37
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "lat" bigger than 90', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "lat" smaller than -90', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: -90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "long" bigger than 180', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: 180.1,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "long" smaller than -180', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: -180.1,
            message: 'Testmessage'
          }).expect(400, done);
        });
      });
    });
  });
  describe('/posts/:id', function () {
    var my_post_id;
    var other_post_id;
    before(function (done) {
      request.post('/posts/?apiKey=thetestuserapikey').send({
        lat: 42,
        long: 21,
        message: 'Testmessage'
      }).end(function (err, res) {
        if (err) {
          done(err);
        } else {
          my_post_id = res.body._id;
          request.post('/posts/?apiKey=theotheruserapikey').send({
            lat: 47,
            long: 11,
            message: 'Testmessage'
          }).end(function (err, res) {
            other_post_id = res.body._id;
            done(err);
          });
        }
      });
    });

    describe('HTTP GET', function () {
      describe('without API key', function () {
        it('should return 200 OK and the correct post', function (done) {
          request.get('/posts/' + other_post_id).expect(200, function (err, res) {
            var post = res.body;
            expect(post.lat).to.equal(47);
            expect(post.long).to.equal(11);
            expect(post.message).to.equal('Testmessage');
            expect(post._id).to.exist;
            expect(post.user).to.equal(otheruser._id.toString());
            expect(post.relevance).to.be.a('number');
            done(err);
          });
        });
        it('should return 404 NOT FOUND with an unkown post id', function (done) {
          request.get('/posts/deadbeef').expect(404, done);
        });
        it('should return 400 BAD REQUEST with an invalid post id', function (done) {
          request.get('/posts/valid_ids_may_only_be_hex_numbers').expect(400, done);
        });
      });
      describe('with API key', function () {
        it('should return 200 OK and the correct post', function (done) {
          request.get('/posts/' + other_post_id + '?apiKey=thetestuserapikey').expect(200, function (err, res) {
            var post = res.body;
            expect(post.lat).to.equal(47);
            expect(post.long).to.equal(11);
            expect(post.message).to.equal('Testmessage');
            expect(post._id).to.exist;
            expect(post.user).to.equal(otheruser._id.toString());
            expect(post.relevance).to.be.a('number');
            done(err);
          });
        });
        it('should return 404 NOT FOUND with an unkown post id', function (done) {
          request.get('/posts/deadbeef?apiKey=thetestuserapikey').expect(404, done);
        });
        it('should return 400 BAD REQUEST with an invalid post id', function (done) {
          request.get('/posts/valid_ids_may_only_be_hex_numbers?apiKey=thetestuserapikey').expect(400, done);
        });
      });
    });
    describe('HTTP POST', function () {
    });
    describe('HTTP DELETE', function () {
      describe('without API key', function () {
        it('should return 403 FORBIDDEN when trying to delete a post', function (done) {
          request.delete('/posts/' + other_post_id).expect(403, function (err, res) {
            if (err) {
              done(err);
            } else {
              posts.findOne({'_id': other_post_id}, function (err, doc) {
                expect(doc).to.exist;
                done(err);
              });
            }
          });
        });
        it('should return 403 FORBIDDEN when trying to delete an unkown post id', function (done) {
          request.delete('/posts/deadbeef').expect(403, done);
        });
        it('should return 403 FORBIDDEN when trying to delete an invalid post id', function (done) {
          request.delete('/posts/valid_ids_may_only_be_hex_numbers').expect(403, done);
        });
      });
      describe('with API key', function () {
        it('should return 204 NO CONTENT when deleting an own post', function (done) {
          request.delete('/posts/' + my_post_id + '?apiKey=thetestuserapikey').expect(204, function (err, res) {
            if (err) {
              done(err);
            } else {
              posts.findOne({'_id': my_post_id}, function (err, doc) {
                expect(doc).to.not.exist;
                done(err);
              });
            }
          });
        });
        it('should return 403 FORBIDDEN when trying to delete a foreign post', function (done) {
          request.delete('/posts/' + other_post_id + '?apiKey=thetestuserapikey').expect(403, function (err, res) {
            if (err) {
              done(err);
            } else {
              posts.findOne({'_id': other_post_id}, function (err, doc) {
                expect(doc).to.exist;
                done(err);
              });
            }
          });
        });
        it('should return 404 NOT FOUND when trying to delete an unkown post id', function (done) {
          request.delete('/posts/deadbeef?apiKey=thetestuserapikey').expect(404, done);
        });
        it('should return 400 BAD REQUEST when trying to delete an invalid post id', function (done) {
          request.delete('/posts/valid_ids_may_only_be_hex_numbers?apiKey=thetestuserapikey').expect(400, done);
        });
      });
    });
  });
});<commit_msg>Added invalid values for limit to tests.<commit_after>var expect = require('chai').expect;
var request = require('supertest')('http://localhost:5000');
var db = require('../database');
var users = db.get('users');
var posts = db.get('posts');

describe('API', function () {
  var testuser;
  var otheruser;
  before(function () {
    users.findOne({apiKey: 'thetestuserapikey'}, function (err, doc) {
      testuser = doc;
    });
    users.findOne({apiKey: 'theotheruserapikey'}, function (err, doc) {
      otheruser = doc;
    });
  });

  describe('Authentication', function () {
    it('should allow access without api key', function (done) {
      request.get('/').expect(200, done);
    });
    it('should not allow access with an invalid api key', function (done) {
      request.get('/?api_key=notavalidapikey').expect(403, done);
    });
    it('should allow access with api key via GET parameter api_key', function (done) {
      request.get('/?api_key=thetestuserapikey').expect(200, done);
    });
    it('should allow access with api key via GET parameter apiKey', function (done) {
      request.get('/?apiKey=thetestuserapikey').expect(200, done);
    });
    it('should redirect /auth/google to Google oAuth2', function (done) {
      request.get('/auth/google').expect(302, function (err, res) {
        expect(res.header['location']).to.match(/^https:\/\/accounts.google.com\/o\/oauth2\/auth/);
        done(err);
      });
    });
    it('should redirect /auth/facebook to Facebook oAuth2', function (done) {
      request.get('/auth/facebook').expect(302, function (err, res) {
        expect(res.header['location']).to.match(/^https:\/\/www.facebook.com\/dialog\/oauth/);
        done(err);
      });
    });
  });

  describe('/posts', function () {
    describe('HTTP GET', function () {
      describe('without API key', function () {
        it('should return 200 OK', function (done) {
          request.get('/posts/').expect(200, done);
        });
        it('should not return more than 25 posts', function (done) {
          request.get('/posts/').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(26);
            done(err);
          });
        });
        it('should not return more than 10 posts if parameter "limit=10" is passed', function (done) {
          request.get('/posts/?limit=10').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(11);
            done(err);
          });
        });
        it('should return 400 BAD REQUEST if parameter "limit" is above 25', function (done) {
          request.get('/posts/?limit=26').expect(400, done);
          request.get('/posts/?limit=100').expect(400, done);
        });
        it('should return 400 BAD REQUEST if parameter "limit" is below 1', function (done) {
          request.get('/posts/?limit=0').expect(400, done);
          request.get('/posts/?limit=-5').expect(400, done);
          request.get('/posts/?limit=-100').expect(400, done);
        });
        it('should return 400 BAD REQUEST if parameter "limit" is not a number', function (done) {
          request.get('/posts/?limit=a').expect(400, done);
          request.get('/posts/?limit=abc').expect(400, done);
          request.get('/posts/?limit=äöü').expect(400, done);
        });
      });
      describe('with API key', function () {
        it('should return 200 OK', function (done) {
          request.get('/posts/?apiKey=thetestuserapikey').expect(200, done);
        });
        it('should not return more than 25 posts', function (done) {
          request.get('/posts/?apiKey=thetestuserapikey').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(26);
            done(err);
          });
        });
        it('should not return more than 10 posts if parameter "limit=10" is passed', function (done) {
          request.get('/posts/?limit=10&apiKey=thetestuserapikey').expect(200, function (err, res) {
            var posts = res.body;
            expect(posts.length).to.be.lessThan(11);
            done(err);
          });
        });
        it('should return 400 BAD REQUEST if parameter "limit" is above 25', function (done) {
          request.get('/posts/?limit=26&apiKey=thetestuserapikey').expect(400, done);
        });
        it('should return 400 BAD REQUEST if parameter "limit" is below 1', function (done) {
          request.get('/posts/?limit=0&apiKey=thetestuserapikey').expect(400, done);
        });
      });
    });
    describe('HTTP POST', function () {
      describe('without API key', function () {
        it('should return 403 FORBIDDEN', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN with missing parameter "lat"', function (done) {
          request.post('/posts/').send({
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN with missing parameter "long"', function (done) {
          request.post('/posts/').send({
            lat: 13,
            message: 'Testmessage'
          }).expect(403, done)
        });
        it('should return 403 FORBIDDEN with missing parameter "message"', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: 37
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "lat" is above 90', function (done) {
          request.post('/posts/').send({
            lat: 90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "lat" is below -90', function (done) {
          request.post('/posts/').send({
            lat: -90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "long" is above 180', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: 180.1,
            message: 'Testmessage'
          }).expect(403, done);
        });
        it('should return 403 FORBIDDEN if parameter "long" is below -180', function (done) {
          request.post('/posts/').send({
            lat: 13,
            long: -180.1,
            message: 'Testmessage'
          }).expect(403, done);
        });
      });
      describe('with API key', function () {
        it('should return 200 OK and return the created post', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: 37,
            message: 'Testmessage'
          }).expect(200, function (err, res) {
            var post = res.body;
            expect(post.user).to.equal(testuser._id.toString());
            expect(post.lat).to.equal(13);
            expect(post.long).to.equal(37);
            expect(post.message).to.equal('Testmessage');
            expect(post._id).to.exist;
            done(err);
          });
        });
        it('should return 400 BAD REQUEST with missing parameter "lat"', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            long: 37,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with missing parameter "long"', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            message: 'Testmessage'
          }).expect(400, done)
        });
        it('should return 400 BAD REQUEST with missing parameter "message"', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: 37
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "lat" bigger than 90', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "lat" smaller than -90', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: -90.1,
            long: 37,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "long" bigger than 180', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: 180.1,
            message: 'Testmessage'
          }).expect(400, done);
        });
        it('should return 400 BAD REQUEST with parameter "long" smaller than -180', function (done) {
          request.post('/posts/?apiKey=thetestuserapikey').send({
            lat: 13,
            long: -180.1,
            message: 'Testmessage'
          }).expect(400, done);
        });
      });
    });
  });
  describe('/posts/:id', function () {
    var my_post_id;
    var other_post_id;
    before(function (done) {
      request.post('/posts/?apiKey=thetestuserapikey').send({
        lat: 42,
        long: 21,
        message: 'Testmessage'
      }).end(function (err, res) {
        if (err) {
          done(err);
        } else {
          my_post_id = res.body._id;
          request.post('/posts/?apiKey=theotheruserapikey').send({
            lat: 47,
            long: 11,
            message: 'Testmessage'
          }).end(function (err, res) {
            other_post_id = res.body._id;
            done(err);
          });
        }
      });
    });

    describe('HTTP GET', function () {
      describe('without API key', function () {
        it('should return 200 OK and the correct post', function (done) {
          request.get('/posts/' + other_post_id).expect(200, function (err, res) {
            var post = res.body;
            expect(post.lat).to.equal(47);
            expect(post.long).to.equal(11);
            expect(post.message).to.equal('Testmessage');
            expect(post._id).to.exist;
            expect(post.user).to.equal(otheruser._id.toString());
            expect(post.relevance).to.be.a('number');
            done(err);
          });
        });
        it('should return 404 NOT FOUND with an unkown post id', function (done) {
          request.get('/posts/deadbeef').expect(404, done);
        });
        it('should return 400 BAD REQUEST with an invalid post id', function (done) {
          request.get('/posts/valid_ids_may_only_be_hex_numbers').expect(400, done);
        });
      });
      describe('with API key', function () {
        it('should return 200 OK and the correct post', function (done) {
          request.get('/posts/' + other_post_id + '?apiKey=thetestuserapikey').expect(200, function (err, res) {
            var post = res.body;
            expect(post.lat).to.equal(47);
            expect(post.long).to.equal(11);
            expect(post.message).to.equal('Testmessage');
            expect(post._id).to.exist;
            expect(post.user).to.equal(otheruser._id.toString());
            expect(post.relevance).to.be.a('number');
            done(err);
          });
        });
        it('should return 404 NOT FOUND with an unkown post id', function (done) {
          request.get('/posts/deadbeef?apiKey=thetestuserapikey').expect(404, done);
        });
        it('should return 400 BAD REQUEST with an invalid post id', function (done) {
          request.get('/posts/valid_ids_may_only_be_hex_numbers?apiKey=thetestuserapikey').expect(400, done);
        });
      });
    });
    describe('HTTP POST', function () {
    });
    describe('HTTP DELETE', function () {
      describe('without API key', function () {
        it('should return 403 FORBIDDEN when trying to delete a post', function (done) {
          request.delete('/posts/' + other_post_id).expect(403, function (err, res) {
            if (err) {
              done(err);
            } else {
              posts.findOne({'_id': other_post_id}, function (err, doc) {
                expect(doc).to.exist;
                done(err);
              });
            }
          });
        });
        it('should return 403 FORBIDDEN when trying to delete an unkown post id', function (done) {
          request.delete('/posts/deadbeef').expect(403, done);
        });
        it('should return 403 FORBIDDEN when trying to delete an invalid post id', function (done) {
          request.delete('/posts/valid_ids_may_only_be_hex_numbers').expect(403, done);
        });
      });
      describe('with API key', function () {
        it('should return 204 NO CONTENT when deleting an own post', function (done) {
          request.delete('/posts/' + my_post_id + '?apiKey=thetestuserapikey').expect(204, function (err, res) {
            if (err) {
              done(err);
            } else {
              posts.findOne({'_id': my_post_id}, function (err, doc) {
                expect(doc).to.not.exist;
                done(err);
              });
            }
          });
        });
        it('should return 403 FORBIDDEN when trying to delete a foreign post', function (done) {
          request.delete('/posts/' + other_post_id + '?apiKey=thetestuserapikey').expect(403, function (err, res) {
            if (err) {
              done(err);
            } else {
              posts.findOne({'_id': other_post_id}, function (err, doc) {
                expect(doc).to.exist;
                done(err);
              });
            }
          });
        });
        it('should return 404 NOT FOUND when trying to delete an unkown post id', function (done) {
          request.delete('/posts/deadbeef?apiKey=thetestuserapikey').expect(404, done);
        });
        it('should return 400 BAD REQUEST when trying to delete an invalid post id', function (done) {
          request.delete('/posts/valid_ids_may_only_be_hex_numbers?apiKey=thetestuserapikey').expect(400, done);
        });
      });
    });
  });
});<|endoftext|>


<commit_before>import Renderer from "./renderer.js";
import GenomeBrowserUtils from "../genome-browser-utils.js";
import {SVG} from "../../core/svg.js";
import LollipopLayout from "../../core/visualisation/lollipop-layout.js";

export default class VariantRenderer extends Renderer {

    constructor(config) {
        super(config);
        this.lollipopLayout = new LollipopLayout({
            maxWidth: this.config.lollipopMaxWidth,
        });
    }

    render(features, options) {
        const topPosition = this.config.lollipopVisible ? this.config.lollipopHeight : this.config.headerHeight;
        let lollipopPositions = [];
        const lollipopRegionWidth = options.requestedRegion.length() * options.pixelBase;
        const lollipopStartX = GenomeBrowserUtils.getFeatureX(options.requestedRegion.start, options);

        if (this.config.lollipopVisible) {
            lollipopPositions = this.lollipopLayout.layout(features || [], options.requestedRegion, lollipopRegionWidth);
        }

        (features || []).forEach((feature, featureIndex) => {
            const group = SVG.addChild(options.svgCanvasFeatures, "g", {});

            // get feature genomic information
            const start = feature.start;
            const end = feature.end;
            const length = Math.max(Math.abs((end - start) + 1), 1);

            // transform to pixel position
            const width = Math.max(length * options.pixelBase, 1);
            const x = GenomeBrowserUtils.getFeatureX(start, options);

            // Get variant information
            const variantColor = this.getValueFromConfig("variantColor", [feature]);
            const variantTooltipTitle = this.getValueFromConfig("variantTooltipTitle", [feature]);
            const variantTooltipText = this.getValueFromConfig("variantTooltipText", [feature]);

            let variantElement = null;

            // Check if lollipops are visible
            if (this.config.lollipopVisible) {
                const lollipopX = lollipopStartX + lollipopPositions[featureIndex];
                const lollipopWidth = Math.min(1, Math.max(0, this.getValueFromConfig("lollipopWidth", [feature])));
                console.log(lollipopWidth);
                const lollipopPath = [
                    `M ${lollipopX},${this.config.lollipopHeight / 8}`,
                    `L ${lollipopX},${this.config.lollipopHeight / 2}`,
                    `L ${x},${3 * this.config.lollipopHeight / 4}`,
                    `L ${x},${this.config.lollipopHeight}`,
                ];
                // Render lollipop stick
                SVG.addChild(group, "path", {
                    "d": lollipopPath.join(" "),
                    "fill": "transparent",
                    "stroke": this.config.lollipopStickColor,
                    "stroke-width": this.config.lollipopStickWidth,
                });
                // Lollipop shape
                variantElement = this.getValueFromConfig("lollipopShape", [
                    feature,
                    group,
                    lollipopX,
                    this.config.lollipopHeight / 8,
                    this.config.lollipopMinWidth + lollipopWidth * (this.config.lollipopMaxWidth - this.config.lollipopMinWidth),
                    variantColor,
                ]);
            } else {
                variantElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": 0,
                    "width": `${width}px`,
                    "height": `${this.config.headerHeight - this.config.dividerHeight - 1}px`,
                    "fill": variantColor,
                    "cursor": "pointer",
                });
            }

            // Display tooltip
            if (variantElement && variantTooltipText) {
                $(variantElement).qtip({
                    content: {
                        text: variantTooltipText,
                        title: variantTooltipTitle || null,
                    },
                    position: {
                        viewport: window,
                        target: "mouse",
                    },
                    style: {
                        width: true,
                        classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                    },
                });
            }

            // Render for each sample in feature.studies[0].samples
            // this.config.sampleNames.forEach((sampleName, index) => {
            (feature.studies[0]?.samples || []).forEach((sampleData, index) => {
                // Only one study is expected, and GT is always the first field in samplesData
                const genotype = sampleData.data[0];
                const sampleGenotypeColor = this.getValueFromConfig("sampleGenotypeColor", [genotype]);
                const sampleGenotypeTooltipTitle = this.getValueFromConfig("sampleGenotypeTooltipTitle", [feature, sampleData]);
                const sampleGenotypeTooltipText = this.getValueFromConfig("sampleGenotypeTooltipText", [feature, sampleData]);

                const sampleGenotypeElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": topPosition + (index * this.config.sampleHeight) + 1,
                    "width": width,
                    "height": this.config.sampleHeight - 2,
                    // "stroke": "#000000",
                    // "stroke-width": 1,
                    // "stroke-opacity": 0.7,
                    "fill": sampleGenotypeColor,
                    "cursor": "pointer",
                    // "data-genotype": genotype,
                });

                if (sampleGenotypeTooltipText) {
                    $(sampleGenotypeElement).qtip({
                        content: {
                            text: sampleGenotypeTooltipText,
                            title: sampleGenotypeTooltipTitle || null,
                        },
                        position: {
                            viewport: window,
                            target: "mouse",
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });
                }
            });
        });
    }

    getDefaultConfig() {
        return {
            infoWidgetId: "id",
            strokeColor: "#555",
            height: 10,
            histogramColor: "#58f3f0",
            // label: GenomeBrowserUtils.variantLabelFormatter,
            variantColor: GenomeBrowserUtils.variantColorFormatter,
            variantTooltipTitle: GenomeBrowserUtils.variantTooltipTitleFormatter,
            variantTooltipText: GenomeBrowserUtils.variantTooltipTextFormatter,
            sampleGenotypeColor: GenomeBrowserUtils.genotypeColorFormatter,
            sampleGenotypeTooltipTitle: GenomeBrowserUtils.sampleGenotypeTooltipTitleFormatter,
            sampleGenotypeTooltipText: GenomeBrowserUtils.sampleGenotypeTooltipTextFormatter,
            // sampleTrackHeight: 20,
            // sampleTrackY: 15,
            // sampleTrackFontSize: 12,
            sampleNames: [],
            sampleHeight: 40,
            // Header configuration
            headerHeight: 20,
            dividerHeight: 2,
            // Lollipop configuration
            lollipopVisible: true,
            lollipopHeight: 40,
            lollipopStickColor: "rgb(164,171,182)",
            lollipopStickWidth: 1,
            lollipopMinWidth: 5,
            lollipopMaxWidth: 10,
            lollipopShape: GenomeBrowserUtils.lollipopShapeFormatter,
            lollipopWidth: 1, // GenomeBrowserUtils.lollipopWidthFormatter,
        };
    }

}
<commit_msg>gb: add lollipop focus in variant renderer #TASK-636<commit_after>import Renderer from "./renderer.js";
import GenomeBrowserUtils from "../genome-browser-utils.js";
import {SVG} from "../../core/svg.js";
import LollipopLayout from "../../core/visualisation/lollipop-layout.js";

export default class VariantRenderer extends Renderer {

    constructor(config) {
        super(config);

        this.lollipopLayout = new LollipopLayout({
            maxWidth: this.config.lollipopMaxWidth,
        });
    }

    render(features, options) {
        const topPosition = this.config.lollipopVisible ? this.config.lollipopHeight : this.config.headerHeight;
        let lollipopPositions = [];
        const lollipopRegionWidth = options.requestedRegion.length() * options.pixelBase;
        const lollipopStartX = GenomeBrowserUtils.getFeatureX(options.requestedRegion.start, options);

        if (this.config.lollipopVisible) {
            lollipopPositions = this.lollipopLayout.layout(features || [], options.requestedRegion, lollipopRegionWidth);
        }

        (features || []).forEach((feature, featureIndex) => {
            const group = SVG.addChild(options.svgCanvasFeatures, "g", {});

            // get feature genomic information
            const start = feature.start;
            const end = feature.end;
            const length = Math.max(Math.abs((end - start) + 1), 1);

            // transform to pixel position
            const width = Math.max(length * options.pixelBase, 1);
            const x = GenomeBrowserUtils.getFeatureX(start, options);

            // Get variant information
            const variantColor = this.getValueFromConfig("variantColor", [feature]);
            const variantTooltipTitle = this.getValueFromConfig("variantTooltipTitle", [feature]);
            const variantTooltipText = this.getValueFromConfig("variantTooltipText", [feature]);

            let variantElement = null;

            // Check if lollipops are visible
            if (this.config.lollipopVisible) {
                const lollipopX = lollipopStartX + lollipopPositions[featureIndex];
                const lollipopWidth = Math.min(1, Math.max(0, this.getValueFromConfig("lollipopWidth", [feature])));
                const lollipopPath = [
                    `M ${lollipopX},${this.config.lollipopHeight / 8}`,
                    `L ${lollipopX},${this.config.lollipopHeight / 2}`,
                    `L ${x},${3 * this.config.lollipopHeight / 4}`,
                    `L ${x},${this.config.lollipopHeight}`,
                ];

                // Render lollipop stick
                const lollipopStick = SVG.addChild(group, "path", {
                    "d": lollipopPath.join(" "),
                    "fill": "transparent",
                    "stroke": this.config.lollipopStickColor,
                    "stroke-width": this.config.lollipopStickWidth,
                });

                // Lollipop shape
                variantElement = this.getValueFromConfig("lollipopShape", [
                    feature,
                    group,
                    lollipopX,
                    this.config.lollipopHeight / 8,
                    this.config.lollipopMinWidth + lollipopWidth * (this.config.lollipopMaxWidth - this.config.lollipopMinWidth),
                    variantColor,
                ]);

                // Register lollipop focus events
                if (this.config.lollipopFocusEnabled) {
                    variantElement.addEventListener("mouseenter", () => {
                        lollipopStick.setAttribute("stroke", this.config.lollipopFocusColor);
                        lollipopStick.setAttribute("stroke-width", this.config.lollipopFocusWidth);
                        variantElement.setAttribute("stroke", this.config.lollipopFocusColor);
                        variantElement.setAttribute("stroke-width", this.config.lollipopFocusWidth);
                    });
                    variantElement.addEventListener("mouseleave", () => {
                        lollipopStick.setAttribute("stroke", this.config.lollipopStickColor);
                        lollipopStick.setAttribute("stroke-width", this.config.lollipopStickWidth);
                        variantElement.setAttribute("stroke-width", 0);
                    });
                }
            } else {
                variantElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": 0,
                    "width": `${width}px`,
                    "height": `${this.config.headerHeight - this.config.dividerHeight - 1}px`,
                    "fill": variantColor,
                    "cursor": "pointer",
                });
            }

            // Display tooltip
            if (variantElement && variantTooltipText) {
                $(variantElement).qtip({
                    content: {
                        text: variantTooltipText,
                        title: variantTooltipTitle || null,
                    },
                    position: {
                        viewport: window,
                        target: "mouse",
                    },
                    style: {
                        width: true,
                        classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                    },
                });
            }

            // Render for each sample in feature.studies[0].samples
            // this.config.sampleNames.forEach((sampleName, index) => {
            (feature.studies[0]?.samples || []).forEach((sampleData, index) => {
                // Only one study is expected, and GT is always the first field in samplesData
                const genotype = sampleData.data[0];
                const sampleGenotypeColor = this.getValueFromConfig("sampleGenotypeColor", [genotype]);
                const sampleGenotypeTooltipTitle = this.getValueFromConfig("sampleGenotypeTooltipTitle", [feature, sampleData]);
                const sampleGenotypeTooltipText = this.getValueFromConfig("sampleGenotypeTooltipText", [feature, sampleData]);

                const sampleGenotypeElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": topPosition + (index * this.config.sampleHeight) + 1,
                    "width": width,
                    "height": this.config.sampleHeight - 2,
                    // "stroke": "#000000",
                    // "stroke-width": 1,
                    // "stroke-opacity": 0.7,
                    "fill": sampleGenotypeColor,
                    "cursor": "pointer",
                    // "data-genotype": genotype,
                });

                if (sampleGenotypeTooltipText) {
                    $(sampleGenotypeElement).qtip({
                        content: {
                            text: sampleGenotypeTooltipText,
                            title: sampleGenotypeTooltipTitle || null,
                        },
                        position: {
                            viewport: window,
                            target: "mouse",
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });
                }
            });
        });
    }

    getDefaultConfig() {
        return {
            infoWidgetId: "id",
            strokeColor: "#555",
            height: 10,
            histogramColor: "#58f3f0",
            // label: GenomeBrowserUtils.variantLabelFormatter,
            variantColor: GenomeBrowserUtils.variantColorFormatter,
            variantTooltipTitle: GenomeBrowserUtils.variantTooltipTitleFormatter,
            variantTooltipText: GenomeBrowserUtils.variantTooltipTextFormatter,
            sampleGenotypeColor: GenomeBrowserUtils.genotypeColorFormatter,
            sampleGenotypeTooltipTitle: GenomeBrowserUtils.sampleGenotypeTooltipTitleFormatter,
            sampleGenotypeTooltipText: GenomeBrowserUtils.sampleGenotypeTooltipTextFormatter,
            // sampleTrackHeight: 20,
            // sampleTrackY: 15,
            // sampleTrackFontSize: 12,
            sampleNames: [],
            sampleHeight: 40,
            // Header configuration
            headerHeight: 20,
            dividerHeight: 2,
            // Lollipop configuration
            lollipopVisible: true,
            lollipopHeight: 40,
            lollipopStickColor: "rgb(164,171,182)",
            lollipopStickWidth: 1,
            lollipopMinWidth: 5,
            lollipopMaxWidth: 10,
            lollipopShape: GenomeBrowserUtils.lollipopShapeFormatter,
            lollipopWidth: 1, // GenomeBrowserUtils.lollipopWidthFormatter,
            // Lollipop focus
            lollipopFocusEnabled: true,
            lollipopFocusWidth: 2,
            lollipopFocusColor: "orange",
        };
    }

}
<|endoftext|>