<commit_before>
function Grid(width, height)
{
  this.width = width;
  this.height = height;
  this.bombs = 0;
  this.flags = 0;

  this.cells = [];

  for (var x = 0; x < width; x++)
  {
    this.cells[x] = [];
    for (var y = 0; y < width; y++)
    {
      this.cells[x][y] = new Case(0,x,y);
    }
  }
}

Grid.prototype.isValidCoordinate = function (x, y)
{
  return x >= 0 && x < this.width && y >= 0 && y < this.height;
};

Grid.prototype.addBomb = function (x, y)
{
  this.cells[x][y].value = 'B';

  for (var i = -1 ; i <= 1 ; i++)
  {
    for (var j = -1 ; j <= 1 ; j++)
    {
      var x2 = x + i;
      var y2 = y + j;

      if (!this.isValidCoordinate(x2, y2))
      {
        continue;
      }

      if (!this.cells[x2][y2].isBomb())
      {
        this.cells[x2][y2].value++;
      }
    }
  }
};

Grid.prototype.addBombs = function (nbBombs)
{
  this.bombs = nbBombs;
  this.flags = nbBombs;

  for (var i = 0; i < nbBombs; i++)
  {
    var x = Math.floor((Math.random() * this.width));
    var y = Math.floor((Math.random() * this.height));

    while (this.cells[x][y].isBomb())
    {
      x = Math.floor((Math.random() * this.width));
      y = Math.floor((Math.random() * this.height));
    }

    this.addBomb(x, y);
  }
};

Grid.prototype.removeCases = function () {
  for (var x = 0; x < width; x++) {
    for (var y = 0; y < width; y++) {
      this.cells[x][y] = null;
    }
  }

};

Grid.prototype.reveal = function (x,y)
{
  if (this.cells[x][y].shown || this.cells[x][y].flagged)
  {
    return [];
  }

  this.cells[x][y].shown = true;

  var listCaseChanged = [this.cells[x][y]];

  if (this.cells[x][y].value === 0)
  {
    for (var i = -1 ; i <= 1 ; i++)
    {
      for (var j = -1 ; j <= 1 ; j++)
      {
        var x2 = x + i;
        var y2 = y + j;

        if (!this.isValidCoordinate(x2, y2))
        {
          continue;
        }

        listCaseChanged = listCaseChanged.concat(this.reveal(x2, y2));
      }
    }
  }

  return listCaseChanged;
};

Grid.prototype.getNbFlagsAround = function (x, y)
{
  var nbFlagsAround = 0;

  for (var i = -1 ; i <= 1 ; i++)
  {
    for (var j = -1 ; j <= 1 ; j++)
    {
      var x2 = x + i;
      var y2 = y + j;

      if (!this.isValidCoordinate(x2, y2))
      {
        continue;
      }

      if (this.cells[x2][y2].flagged)
      {
        nbFlagsAround++;
      }
    }
  }

  return nbFlagsAround;
};

Grid.prototype.quickReveal = function (x, y)
{
  if (this.cells[x][y].isBomb())
  {
    return {cases: [], lost: true};
  }

  if (this.getNbFlagsAround(x, y) != this.cells[x][y].value)
  {
      return {cases: [], lost: false};
  }

  var lost = false;
  var listCaseChanged = [];

  for (var i = -1 ; i <= 1 ; i++)
  {
    for (var j = -1 ; j <= 1 ; j++)
    {
      var x2 = x + i;
      var y2 = y + j;

      if (!this.isValidCoordinate(x2, y2) || this.cells[x2][y2].flagged)
      {
        if (!this.cells[x2][y2].isBomb())
        {
          listCaseChanged.push(this.cells[x2][y2]);
        }

        continue;
      }

      if (this.cells[x2][y2].isBomb())
      {
        lost = true;
      }

      listCaseChanged = listCaseChanged.concat(this.reveal(x2, y2));
    }
  }

  return {cases: listCaseChanged, lost: lost};
};
<commit_msg>fix double click and add Grid.getBombs<commit_after>
function Grid(width, height)
{
  this.width = width;
  this.height = height;
  this.bombs = 0;
  this.flags = 0;

  this.cells = [];

  for (var x = 0; x < width; x++)
  {
    this.cells[x] = [];
    for (var y = 0; y < width; y++)
    {
      this.cells[x][y] = new Case(0,x,y);
    }
  }
}

Grid.prototype.isValidCoordinate = function (x, y)
{
  return x >= 0 && x < this.width && y >= 0 && y < this.height;
};

Grid.prototype.addBomb = function (x, y)
{
  this.cells[x][y].value = 'B';

  for (var i = -1 ; i <= 1 ; i++)
  {
    for (var j = -1 ; j <= 1 ; j++)
    {
      var x2 = x + i;
      var y2 = y + j;

      if (!this.isValidCoordinate(x2, y2))
      {
        continue;
      }

      if (!this.cells[x2][y2].isBomb())
      {
        this.cells[x2][y2].value++;
      }
    }
  }
};

Grid.prototype.addBombs = function (nbBombs)
{
  this.bombs += nbBombs;
  this.flags += nbBombs;

  for (var i = 0; i < nbBombs; i++)
  {
    var x = Math.floor((Math.random() * this.width));
    var y = Math.floor((Math.random() * this.height));

    while (this.cells[x][y].isBomb())
    {
      x = Math.floor((Math.random() * this.width));
      y = Math.floor((Math.random() * this.height));
    }

    this.addBomb(x, y);
  }
};

Grid.prototype.removeCases = function () {
  for (var x = 0; x < width; x++) {
    for (var y = 0; y < width; y++) {
      this.cells[x][y] = null;
    }
  }

};

Grid.prototype.reveal = function (x,y)
{
  if (this.cells[x][y].shown || this.cells[x][y].flagged)
  {
    return [];
  }

  this.cells[x][y].shown = true;

  var listCaseChanged = [this.cells[x][y]];

  if (this.cells[x][y].value === 0)
  {
    for (var i = -1 ; i <= 1 ; i++)
    {
      for (var j = -1 ; j <= 1 ; j++)
      {
        var x2 = x + i;
        var y2 = y + j;

        if (!this.isValidCoordinate(x2, y2))
        {
          continue;
        }

        listCaseChanged = listCaseChanged.concat(this.reveal(x2, y2));
      }
    }
  }

  return listCaseChanged;
};

Grid.prototype.getNbFlagsAround = function (x, y)
{
  var nbFlagsAround = 0;

  for (var i = -1 ; i <= 1 ; i++)
  {
    for (var j = -1 ; j <= 1 ; j++)
    {
      var x2 = x + i;
      var y2 = y + j;

      if (!this.isValidCoordinate(x2, y2))
      {
        continue;
      }

      if (this.cells[x2][y2].flagged)
      {
        nbFlagsAround++;
      }
    }
  }

  return nbFlagsAround;
};

Grid.prototype.quickReveal = function (x, y)
{
  if (this.cells[x][y].isBomb())
  {
    return {cases: [], lost: true};
  }

  if (this.getNbFlagsAround(x, y) != this.cells[x][y].value)
  {
      return {cases: [], lost: false};
  }

  var lost = false;
  var listCaseChanged = [];

  for (var i = -1 ; i <= 1 ; i++)
  {
    for (var j = -1 ; j <= 1 ; j++)
    {
      var x2 = x + i;
      var y2 = y + j;

      if (!this.isValidCoordinate(x2, y2))
      {
        continue;
      }

      if (this.cells[x2][y2].flagged && !this.cells[x2][y2].isBomb())
      {
        listCaseChanged.push(this.cells[x2][y2]);
        continue;
      }

      if (this.cells[x2][y2].isBomb())
      {
        lost = true;
      }

      listCaseChanged = listCaseChanged.concat(this.reveal(x2, y2));
    }
  }

  return {cases: listCaseChanged, lost: lost};
};

Grid.prototype.getBombs = function ()
{
  var bombList = [];

  for (var cell in this.cells) {
    if (cell.isBomb())
    {
      bombList.push(cell);
    }
  }

  return bombList;
};
<|endoftext|>


<commit_before>transporter.module.create('core/testFramework', function(requires) {}, function(thisModule) {

thisModule.addSlots(avocado, function(add) {

  add.creator('testCase', {}, {category: ['test framework']}, {comment: 'Copied a lot of this from the LK test framework. Don\'t want\nto just use theirs because we might not always be using LK.'});

});

thisModule.addSlots(avocado.testCase, function(add) {

  add.data('shouldRun', true);

  add.method('initialize', function (testResult, optTestSelector) {
		this.result = testResult || Object.newChildOf(this.resultProto);
		this.currentSelector = optTestSelector;
		this.statusUpdateFunc = null;
	});

  add.method('log', function (aString) {
    console.log(aString);
	});

  add.method('id', function () { return this.name() + '>>' + this.currentSelector; });

  add.method('setUp', function () {});

  add.method('tearDown', function () {});

  add.method('assert', function (bool, msg) {
        if (bool) return;
        msg = " assert failed " + msg ? '(' + msg + ')' : '';
		this.show(this.id() + msg);
        throw {isAssertion: true, message: msg, toString: function() { return msg }}
    });

  add.method('assertEqual', function (firstValue, secondValue, msg) {
    if (! this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " != " + secondValue + ") "};
    }
  });

  add.method('assertIdentity', function (firstValue, secondValue, msg) {
		if (firstValue === secondValue) { return; }
		this.assert(false, (msg ? msg : '') + ' (' + firstValue +' !== ' + secondValue +')');
	});

  add.method('allTestSelectors', function () {
    var functionNames = [];
    for (var name in this) {
      if (name.startsWith('test') && typeof this[name] === 'function') {
        functionNames.push(name);
      }
    }
    return functionNames;
  });

  add.method('toString', function ($super) {
	    return $super() + "(" + this.timeToRun +")";
	});

  add.method('show', function (string) { this.log(string); });

  add.method('running', function () {
		this.show('Running ' + this.id());
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'running');
	});

  add.method('success', function () {
		this.show(this.id()+ ' done', 'color: green;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'success');
	});

  add.method('failure', function (error) {
		this._errorOccured = true;
		var message = error.toString();
		var file = error.sourceURL || error.fileName;
		var line = error.line || error.lineNumber;
		message += ' (' + file + ':' + line + ')';
		message += ' in ' + this.id();
		this.show(message , 'color: red;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'failure', message);
	});

  add.method('createTests', function () {
    return this.allTestSelectors().collect(function(sel) {
      return this.create(this.result, sel);
    }, this);
  });

  add.method('name', function () {
    return reflect(this).name();
  });

  add.method('create', function (testResult, optTestSelector) {
    return Object.newChildOf(this, testResult, optTestSelector);
  });

  add.method('createAndRun', function () {
    var testCase = this.create();
    testCase.runAll();
    var result = testCase.result;
    result.testCase = testCase;
    return result;
  });

  add.method('runTest', function (aSelector) {
    if (!this.shouldRun) return;
		this.currentSelector = aSelector || this.currentSelector;

		this.running();
		try {
			this.setUp();
			this[this.currentSelector]();
			this.result.addSuccess(this.name(), this.currentSelector); // fixed by Adam to say this.name() instead of this.constructor.type
			this.success();
		} catch (e) {
			this.result.addFailure(this.name(), this.currentSelector, e); // fixed by Adam to say this.name() instead of this.constructor.type
			this.failure(e);
		} finally {
			try {
				this.tearDown();
			} catch(e) {
				this.log('Couldn\'t run tearDown for ' + this.id() + ' ' + printError(e));
			}
		}
	});

  add.method('inspect', function () {
    return this.name();
  });

  add.method('areEqual', function (firstValue, secondValue) {
    if (firstValue === secondValue) { return true; }
    if (firstValue && firstValue.equals && firstValue.equals(secondValue)) { return true; } // changed this to check a general 'equals' method. -- Adam
    if (firstValue == secondValue) { return true; }
    return false;
  });

  add.method('assertNotEqual', function (firstValue, secondValue, msg) {
    if (this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " == " + secondValue + ") "};
    }
  });

  add.method('assertThrowsException', function (func, msg) {
    var thrown = false;
    try {
      func();
    } catch (ex) {
      thrown = true;
    }
    this.assert(thrown, msg); // can't put this inside the try because it works by throwing an exception;
  });

  add.method('runAll', function (statusUpdateFunc) {
		var tests = this.createTests()
		var t1 = new Date().getTime();
		tests.forEach(function(test) {
			test.statusUpdateFunc = statusUpdateFunc;
			test.runTest();
		});
		var t2 = new Date().getTime();
		this.result.setTimeToRun(this.name(), t2 - t1);
	});

  add.creator('resultProto', {}, {category: ['results']});

  add.method('createAndRunAndShowResult', function () {
    avocado.ui.showNextTo(this, this.createAndRun(), this);
  }, {category: ['user interface', 'commands']});

  add.method('getTestCaseObject', function (evt) {
    avocado.ui.grab(reflect(this), evt);
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create(this);
    cmdList.addItem({label: 'run', pluralLabel: 'run tests', go: this.createAndRunAndShowResult});
    cmdList.addLine();
    cmdList.addItem({label: 'get test case object', go: this.getTestCaseObject});
    return cmdList;
  }, {category: ['user interface', 'commands']});

  add.method('buttonCommands', function () {
    return avocado.command.list.create(this, [
      avocado.command.create('Run', this.createAndRunAndShowResult)
    ]);
  }, {category: ['user interface', 'commands']});

  add.method('addGlobalCommandsTo', function (cmdList) {
    cmdList.addLine();

    cmdList.addItem(["get tests", function(evt) {
      avocado.ui.showObjects(this.allAvocadoTestCasePrototypes(), "test cases for avocado");
    }.bind(this)]);
  }, {category: ['user interface', 'commands']});

  add.method('allAvocadoTestCasePrototypes', function () {
    // aaa - This should be replaced with a more general mechanism for finding tests.
    return [
      avocado.dictionary.tests,
      avocado.set.tests,
      mirror.tests,
      transporter.tests,
      avocado.objectGraphWalker.tests,
      exitValueOf.tests,
      avocado.enumerator.tests,
      avocado.range.tests,
      avocado.notifier.tests,
      avocado.stringBuffer.tests,
      String.prototype.tests,
      Array.prototype.tests,
      avocado.dependencies.tests,
      avocado.list.tests,
      avocado.prettyPrinter.tests,
      organization.tests,
      avocado.process.tests,
      avocado.remoteMirror.tests
    ];
  });

});

thisModule.addSlots(avocado.testCase.resultProto, function(add) {

  add.method('initialize', function () {
		this.failed = [];
		this.succeeded = [];
		this.timeToRun = {};
	});

  add.method('setTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName]= time;
	});

  add.method('getTimeToRun', function (testCaseName) {
	    return this.timeToRun[testCaseName];
	});

  add.method('addSuccess', function (className, selector) {
		this.succeeded.push({
				classname: className,
				selector: selector});
	});

  add.method('addFailure', function (className, selector, error) {
	  // Better error message than the standard LK one.
	  var failure = {
				classname: className,
				selector: selector,
				err: error
		};
		failure.toString = this.failureDescription.bind(this, failure);
		this.failed.push(failure);
  });

  add.method('failureDescription', function (failure) {
    var s = avocado.stringBuffer.create(failure.selector).append(" failed ");
    if (failure.err.sourceURL !== undefined) {
      s.append("(").append(this.getFileNameFromError(failure.err));
      if (failure.err.line !== undefined) {
        s.append(":").append(failure.err.line);
      }
      s.append("): ");
    }
    s.append(typeof(failure.err.message) !== 'undefined' ? failure.err.message : failure.err);
    return s.toString();
  });

  add.method('runs', function () {
		if (!this.failed)
			return 0;
		return this.failed.length + this.succeeded.length;
	});

  add.method('toString', function () {
    return this.inspect();
	});

  add.method('getFileNameFromError', function (err) {
    if (!err.sourceURL) { return ""; }
    var path = err.sourceURL.split("/");
    return path[path.length - 1].split("?")[0];
	});

  add.method('failureList', function () {
		var result = this.failed.collect(function(ea) {
			return ea.classname + '.' + ea.selector + '\n  -->' + ea.err.constructor.name + ":" + ea.err.message  +
	            ' in ' + this.getFileNameFromError(ea.err) +
	            (ea.err.line ? ' ( Line '+ ea.err.line + ')' : "");
		}, this);
		return result;
	});

  add.method('successList', function () {
		return this.succeeded.collect(function(ea) { return ea.classname + '.' + ea.selector });
	});

  add.method('totalTimeToRun', function () {
    var total = 0;
    for (var name in this.timeToRun) {
      if (this.timeToRun.hasOwnProperty(name)) {
        var t = this.timeToRun[name];
        if (typeof(t) === 'number') {
          total += t;
        }
      }
    }
    return total;
  });

  add.method('inspect', function () {
    return this.testCase.name() + "(" + this.totalTimeToRun() + " ms)";
  });

  add.method('allPassed', function () {
    return this.failed.length === 0;
  });

  add.method('anyFailed', function () {
    return ! this.allPassed();
  });

  add.method('getErrorObjects', function (evt) {
    this.failed.each(function(f) {
      avocado.ui.grab(reflect(f.err), evt);
    });
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create();
    cmdList.addItem({label: 'get error objects', go: this.getErrorObjects.bind(this), isApplicable: this.anyFailed.bind(this)});
    return cmdList;
  }, {category: ['user interface', 'commands']});

});

});
<commit_msg>Extended the test framework to understand asynchronous tests.<commit_after>transporter.module.create('core/testFramework', function(requires) {}, function(thisModule) {

thisModule.addSlots(avocado, function(add) {

  add.creator('testCase', {}, {category: ['test framework']}, {comment: 'Copied a lot of this from the LK test framework. Don\'t want\nto just use theirs because we might not always be using LK.'});

});

thisModule.addSlots(avocado.testCase, function(add) {

  add.data('shouldRun', true);

  add.method('initialize', function (testResult, optTestSelector) {
		this.result = testResult || Object.newChildOf(this.resultProto);
		this.currentSelector = optTestSelector;
		this.statusUpdateFunc = null;
	});

  add.method('log', function (aString) {
    console.log(aString);
	});

  add.method('id', function () { return this.name() + '>>' + this.currentSelector; });

  add.method('setUp', function () {});

  add.method('tearDown', function () {});

  add.method('assert', function (bool, msg) {
        if (bool) return;
        msg = " assert failed " + msg ? '(' + msg + ')' : '';
		this.show(this.id() + msg);
        throw {isAssertion: true, message: msg, toString: function() { return msg }}
    });

  add.method('assertEqual', function (firstValue, secondValue, msg) {
    if (! this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " != " + secondValue + ") "};
    }
  });

  add.method('assertIdentity', function (firstValue, secondValue, msg) {
		if (firstValue === secondValue) { return; }
		this.assert(false, (msg ? msg : '') + ' (' + firstValue +' !== ' + secondValue +')');
	});

  add.method('allTestSelectors', function () {
    var functionNames = [];
    for (var name in this) {
      if ((name.startsWith('test') || name.startsWith('asynchronouslyTest')) && typeof this[name] === 'function') {
        functionNames.push(name);
      }
    }
    return functionNames;
  });

  add.method('toString', function ($super) {
	    return $super() + "(" + this.timeToRun +")";
	});

  add.method('show', function (string) { this.log(string); });

  add.method('running', function () {
		this.show('Running ' + this.id());
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'running');
	});

  add.method('success', function () {
		this.show(this.id()+ ' done', 'color: green;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'success');
	});

  add.method('failure', function (error) {
		this._errorOccured = true;
		var message = error.toString();
		var file = error.sourceURL || error.fileName;
		var line = error.line || error.lineNumber;
		message += ' (' + file + ':' + line + ')';
		message += ' in ' + this.id();
		this.show(message , 'color: red;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'failure', message);
	});

  add.method('createTests', function () {
    return this.allTestSelectors().collect(function(sel) {
      return this.create(this.result, sel);
    }, this);
  });

  add.method('name', function () {
    return reflect(this).name();
  });

  add.method('create', function (testResult, optTestSelector) {
    return Object.newChildOf(this, testResult, optTestSelector);
  });

  add.method('createAndRun', function () {
    var testCase = this.create();
    testCase.runAll();
    var result = testCase.result;
    result.testCase = testCase;
    return result;
  });

  add.method('runTest', function (callback) {
    if (!this.shouldRun) return;

		this.running();
		try {
			this.setUp();
			this.runTestFunction(function() {
  			this.result.addSuccess(this.name(), this.currentSelector); // fixed by Adam to say this.name() instead of this.constructor.type
  			this.success();
  			callback();
			}.bind(this));
		} catch (e) {
			this.result.addFailure(this.name(), this.currentSelector, e); // fixed by Adam to say this.name() instead of this.constructor.type
			this.failure(e);
		  callback();
		} finally {
		  this.doTearDown();
		}
	});

  add.method('runTestFunction', function (callback) {
		var testFn = this[this.currentSelector];
		if (this.currentSelector.startsWith('asynchronouslyTest')) {
		  testFn.call(this, callback);
	  } else {
	    testFn.call(this);
	    callback();
	  }
  });

  add.method('doTearDown', function () {
		try {
			this.tearDown();
		} catch(e) {
      var errStr = "" + e.constructor.name + ": ";
      for (var i in e) { s += i + ": " + String(e[i]) + ", " }; // get everything out....
			this.log('Couldn\'t run tearDown for ' + this.id() + ' ' + printError(errStr));
		}
  });

  add.method('inspect', function () {
    return this.name();
  });

  add.method('areEqual', function (firstValue, secondValue) {
    if (firstValue === secondValue) { return true; }
    if (firstValue && firstValue.equals && firstValue.equals(secondValue)) { return true; } // changed this to check a general 'equals' method. -- Adam
    if (firstValue == secondValue) { return true; }
    return false;
  });

  add.method('assertNotEqual', function (firstValue, secondValue, msg) {
    if (this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " == " + secondValue + ") "};
    }
  });

  add.method('assertThrowsException', function (func, msg) {
    var thrown = false;
    try {
      func();
    } catch (ex) {
      thrown = true;
    }
    this.assert(thrown, msg); // can't put this inside the try because it works by throwing an exception;
  });

  add.method('runAll', function (statusUpdateFunc) {
		var tests = this.createTests();
		var totalTime = 0;
		tests.forEach(function(test) {
  		var t1 = new Date().getTime();
			test.statusUpdateFunc = statusUpdateFunc;
			test.runTest(function() {
    		var t2 = new Date().getTime();
    		this.result.incrementTimeToRun(this.name(), t2 - t1);
			}.bind(this));
		}.bind(this));
	});

  add.creator('resultProto', {}, {category: ['results']});

  add.method('createAndRunAndShowResult', function () {
    avocado.ui.showNextTo(this, this.createAndRun(), this);
  }, {category: ['user interface', 'commands']});

  add.method('getTestCaseObject', function (evt) {
    avocado.ui.grab(reflect(this), evt);
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create(this);
    cmdList.addItem({label: 'run', pluralLabel: 'run tests', go: this.createAndRunAndShowResult});
    cmdList.addLine();
    cmdList.addItem({label: 'get test case object', go: this.getTestCaseObject});
    return cmdList;
  }, {category: ['user interface', 'commands']});

  add.method('buttonCommands', function () {
    return avocado.command.list.create(this, [
      avocado.command.create('Run', this.createAndRunAndShowResult)
    ]);
  }, {category: ['user interface', 'commands']});

  add.method('addGlobalCommandsTo', function (cmdList) {
    cmdList.addLine();

    cmdList.addItem(["get tests", function(evt) {
      avocado.ui.showObjects(this.allAvocadoTestCasePrototypes(), "test cases for avocado");
    }.bind(this)]);
  }, {category: ['user interface', 'commands']});

  add.method('allAvocadoTestCasePrototypes', function () {
    // aaa - This should be replaced with a more general mechanism for finding tests.
    return [
      avocado.dictionary.tests,
      avocado.set.tests,
      mirror.tests,
      transporter.tests,
      avocado.objectGraphWalker.tests,
      exitValueOf.tests,
      avocado.enumerator.tests,
      avocado.range.tests,
      avocado.notifier.tests,
      avocado.stringBuffer.tests,
      String.prototype.tests,
      Array.prototype.tests,
      avocado.dependencies.tests,
      avocado.list.tests,
      avocado.prettyPrinter.tests,
      organization.tests,
      avocado.process.tests,
      avocado.remoteMirror.tests
    ];
  });

});

thisModule.addSlots(avocado.testCase.resultProto, function(add) {

  add.method('initialize', function () {
		this.failed = [];
		this.succeeded = [];
		this.timeToRun = {};
	});

  add.method('incrementTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName] = (this.timeToRun[testCaseName] || 0) + time;
	});

  add.method('setTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName] = time;
	});

  add.method('getTimeToRun', function (testCaseName) {
	    return this.timeToRun[testCaseName];
	});

  add.method('addSuccess', function (className, selector) {
		this.succeeded.push({
				classname: className,
				selector: selector});
	});

  add.method('addFailure', function (className, selector, error) {
	  // Better error message than the standard LK one.
	  var failure = {
				classname: className,
				selector: selector,
				err: error
		};
		failure.toString = this.failureDescription.bind(this, failure);
		this.failed.push(failure);
  });

  add.method('failureDescription', function (failure) {
    var s = avocado.stringBuffer.create(failure.selector).append(" failed ");
    if (failure.err.sourceURL !== undefined) {
      s.append("(").append(this.getFileNameFromError(failure.err));
      if (failure.err.line !== undefined) {
        s.append(":").append(failure.err.line);
      }
      s.append("): ");
    }
    s.append(typeof(failure.err.message) !== 'undefined' ? failure.err.message : failure.err);
    return s.toString();
  });

  add.method('runs', function () {
		if (!this.failed)
			return 0;
		return this.failed.length + this.succeeded.length;
	});

  add.method('toString', function () {
    return this.inspect();
	});

  add.method('getFileNameFromError', function (err) {
    if (!err.sourceURL) { return ""; }
    var path = err.sourceURL.split("/");
    return path[path.length - 1].split("?")[0];
	});

  add.method('failureList', function () {
		var result = this.failed.collect(function(ea) {
			return ea.classname + '.' + ea.selector + '\n  -->' + ea.err.constructor.name + ":" + ea.err.message  +
	            ' in ' + this.getFileNameFromError(ea.err) +
	            (ea.err.line ? ' ( Line '+ ea.err.line + ')' : "");
		}, this);
		return result;
	});

  add.method('successList', function () {
		return this.succeeded.collect(function(ea) { return ea.classname + '.' + ea.selector });
	});

  add.method('totalTimeToRun', function () {
    var total = 0;
    for (var name in this.timeToRun) {
      if (this.timeToRun.hasOwnProperty(name)) {
        var t = this.timeToRun[name];
        if (typeof(t) === 'number') {
          total += t;
        }
      }
    }
    return total;
  });

  add.method('inspect', function () {
    return this.testCase.name() + "(" + this.totalTimeToRun() + " ms)";
  });

  add.method('allPassed', function () {
    return this.failed.length === 0;
  });

  add.method('anyFailed', function () {
    return ! this.allPassed();
  });

  add.method('getErrorObjects', function (evt) {
    this.failed.each(function(f) {
      avocado.ui.grab(reflect(f.err), evt);
    });
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create();
    cmdList.addItem({label: 'get error objects', go: this.getErrorObjects.bind(this), isApplicable: this.anyFailed.bind(this)});
    return cmdList;
  }, {category: ['user interface', 'commands']});

});

});
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/* eslint-env node */
'use strict';

const fs = require( 'fs' );
const jpeg = require( 'jpeg-js' ); // eslint-disable-line require-statement-match
const grunt = require( 'grunt' );
const mipmapDownscale = require( '../../../chipper/js/common/mipmapDownscale' );
const pngjs = require( 'pngjs' );

/**
 * Responsible for converting a single PNG/JPEG file to a structured list of mipmapped versions of it, each
 * at half the scale of the previous version.
 *
 * Level 0 is the original image, level 1 is a half-size image, level 2 is a quarter-size image, etc.
 *
 * For each level, a preferred encoding (PNG/JPEG) is determined. If the image doesn't need alpha information and
 * the JPEG base64 is smaller, the JPEG encoding will be used (PNG otherwise).
 *
 * The resulting object for each mipmap level will be of the form:
 * {
 *   width: {number} - width of the image provided by this level of detail
 *   height: {number} - width of the image provided by this level of detail
 *   data: {Buffer} - 1-dimensional row-major buffer holding RGBA information for the level as an array of bytes 0-255.
 *                    e.g. buffer[2] will be the blue component of the top-left pixel, buffer[4] is the red component
 *                    for the pixel to the right, etc.
 *   url: {string} - Data URL for the preferred image data
 *   buffer: {Buffer} - Raw bytes for the preferred image data (could be written to file and opened as an image)
 *   <pngURL, pngBuffer, jpgURL, jpgBuffer may also be available, but is not meant for general use>
 * }
 *
 * @param {string} filename
 * @param {number} maxLevel - An integer denoting the maximum level of detail that should be included, or -1 to include
 *                            all levels up to and including a 1x1 image.
 * @param {number} quality - An integer from 1-100 determining the quality of the image. Currently only used for the
 *                           JPEG encoding quality.
 * @returns {Prmomise} - Will be resolved with mipmaps: {Array} (consisting of the mipmap objects, mipmaps[0] will be level 0)
 */
module.exports = function createMipmap( filename, maxLevel, quality ) {
  return new Promise( ( resolve, reject ) => {
    const mipmaps = [];

    // kick everything off
    const suffix = filename.slice( -4 );
    if ( suffix === '.jpg' ) {
      loadJPEG();
    }
    else if ( suffix === '.png' ) {
      loadPNG();
    }
    else {
      throw new Error( 'unknown image type: ' + filename );
    }

    // Loads / decodes the initial JPEG image, and when done proceeds to the mipmapping
    function loadJPEG() {
      var imageData = jpeg.decode( grunt.file.read( filename ) );

      mipmaps.push( {
        data: imageData.data,
        width: imageData.width,
        height: imageData.height
      } );

      startMipmapCreation();
    }

    // Loads / decodes the initial PNG image, and when done proceeds to the mipmapping
    function loadPNG() {
      var src = fs.createReadStream( filename );

      var basePNG = new pngjs.PNG( {
        // if we need a specific filter type, put it here
      } );

      basePNG.on( 'error', function( err ) {
        throw err;
      } );

      basePNG.on( 'parsed', function() {
        mipmaps.push( {
          data: basePNG.data,
          width: basePNG.width,
          height: basePNG.height
        } );

        startMipmapCreation();
      } );

      // pass the stream to pngjs
      src.pipe( basePNG );
    }

    /**
     * @param {Buffer} data - Should have 4*width*height elements
     * @param {number} width
     * @param {number} height
     * @param {number} quality - Out of 100
     * @param {function} callback - function( buffer )
     */
    function outputJPEG( data, width, height, quality, callback ) {
      var encodedOuput = jpeg.encode( {
        data: data,
        width: width,
        height: height
      }, quality );
      callback( encodedOuput.data );
    }

    /**
     * @param {Buffer} data - Should have 4*width*height elements
     * @param {number} width
     * @param {number} height
     * @param {function} callback - function( buffer )
     */
    function outputPNG( data, width, height, callback ) {
      // provides width/height so it is initialized with the correct-size buffer
      var png = new pngjs.PNG( {
        width: width,
        height: height
      } );

      // copy our image data into the pngjs.PNG's data buffer;
      data.copy( png.data, 0, 0, data.length );

      // will concatenate the buffers from the stream into one once it is finished
      var buffers = [];
      png.on( 'data', function( buffer ) {
        buffers.push( buffer );
      } );
      png.on( 'end', function() {
        var buffer = Buffer.concat( buffers );

        callback( buffer );
      } );
      png.on( 'error', function( err ) {
        throw err;
      } );

      // kick off the encoding of the PNG
      png.pack();
    }

    // called when our mipmap[0] level is loaded by decoding the main image (creates the mipmap levels)
    function startMipmapCreation() {
      // When reduced to 0, we'll be done with encoding (and can call our callback). Needed because they are asynchronous.
      var encodeCounter = 1;

      // Alpha detection on the level-0 image to see if we can swap jpg for png
      var hasAlpha = false;
      for ( var i = 3; i < mipmaps[ 0 ].data.length; i += 4 ) {
        if ( mipmaps[ 0 ].data[ i ] < 255 ) {
          hasAlpha = true;
          break;
        }
      }

      // called when all of encoding is complete
      function encodingComplete() {
        grunt.log.debug( 'mipmapped ' + filename + ( maxLevel >= 0 ? ' to level ' + maxLevel : '' ) + ' with quality: ' + quality );

        for ( var level = 0; level < mipmaps.length; level++ ) {
          // for now, make .url point to the smallest of the two (unless we have an alpha channel need)
          var usePNG = hasAlpha || mipmaps[ level ].jpgURL.length > mipmaps[ level ].pngURL.length;
          mipmaps[ level ].url = usePNG ? mipmaps[ level ].pngURL : mipmaps[ level ].jpgURL;
          mipmaps[ level ].buffer = usePNG ? mipmaps[ level ].pngBuffer : mipmaps[ level ].jpgBuffer;

          grunt.log.debug( 'level ' + level + ' (' + ( usePNG ? 'PNG' : 'JPG' ) + ' ' +
                           mipmaps[ level ].width + 'x' + mipmaps[ level ].height + ') base64: ' +
                           mipmaps[ level ].url.length + ' bytes ' );
        }

        resolve( mipmaps );
      }

      // kicks off asynchronous encoding for a specific level
      function encodeLevel( level ) {
        encodeCounter++;
        outputPNG( mipmaps[ level ].data, mipmaps[ level ].width, mipmaps[ level ].height, function( buffer ) {
          mipmaps[ level ].pngBuffer = buffer;
          mipmaps[ level ].pngURL = 'data:image/png;base64,' + buffer.toString( 'base64' );
          if ( --encodeCounter === 0 ) {
            encodingComplete();
          }
        } );

        // only encode JPEG if it has no alpha
        if ( !hasAlpha ) {
          encodeCounter++;
          outputJPEG( mipmaps[ level ].data, mipmaps[ level ].width, mipmaps[ level ].height, quality, function( buffer ) {
            mipmaps[ level ].jpgBuffer = buffer;
            mipmaps[ level ].jpgURL = 'data:image/jpeg;base64,' + buffer.toString( 'base64' );
            if ( --encodeCounter === 0 ) {
              encodingComplete();
            }
          } );
        }
      }

      // encode all levels, and compute rasters for levels 1-N
      encodeLevel( 0 );
      function finestMipmap() {
        return mipmaps[ mipmaps.length - 1 ];
      }

      // bail if we already have a 1x1 image, or if we reach the maxLevel (recall maxLevel===-1 means no maximum level)
      while ( ( mipmaps.length - 1 < maxLevel || maxLevel < 0 ) && ( finestMipmap().width > 1 || finestMipmap().height > 1 ) ) {
        var level = mipmaps.length;
        mipmaps.push( mipmapDownscale( finestMipmap(), function( width, height ) {
          return new Buffer( 4 * width * height );
        } ) );
        encodeLevel( level );
      }

      // just in case everything happened synchronously
      if ( --encodeCounter === 0 ) {
        encodingComplete();
      }
    }
  } );
};
<commit_msg>Pass a buffer to jpeg.decode (not a string), see https://github.com/phetsims/gene-expression-essentials/issues/119<commit_after>// Copyright 2015, University of Colorado Boulder

/* eslint-env node */
'use strict';

const fs = require( 'fs' );
const jpeg = require( 'jpeg-js' ); // eslint-disable-line require-statement-match
const grunt = require( 'grunt' );
const mipmapDownscale = require( '../../../chipper/js/common/mipmapDownscale' );
const pngjs = require( 'pngjs' );

/**
 * Responsible for converting a single PNG/JPEG file to a structured list of mipmapped versions of it, each
 * at half the scale of the previous version.
 *
 * Level 0 is the original image, level 1 is a half-size image, level 2 is a quarter-size image, etc.
 *
 * For each level, a preferred encoding (PNG/JPEG) is determined. If the image doesn't need alpha information and
 * the JPEG base64 is smaller, the JPEG encoding will be used (PNG otherwise).
 *
 * The resulting object for each mipmap level will be of the form:
 * {
 *   width: {number} - width of the image provided by this level of detail
 *   height: {number} - width of the image provided by this level of detail
 *   data: {Buffer} - 1-dimensional row-major buffer holding RGBA information for the level as an array of bytes 0-255.
 *                    e.g. buffer[2] will be the blue component of the top-left pixel, buffer[4] is the red component
 *                    for the pixel to the right, etc.
 *   url: {string} - Data URL for the preferred image data
 *   buffer: {Buffer} - Raw bytes for the preferred image data (could be written to file and opened as an image)
 *   <pngURL, pngBuffer, jpgURL, jpgBuffer may also be available, but is not meant for general use>
 * }
 *
 * @param {string} filename
 * @param {number} maxLevel - An integer denoting the maximum level of detail that should be included, or -1 to include
 *                            all levels up to and including a 1x1 image.
 * @param {number} quality - An integer from 1-100 determining the quality of the image. Currently only used for the
 *                           JPEG encoding quality.
 * @returns {Prmomise} - Will be resolved with mipmaps: {Array} (consisting of the mipmap objects, mipmaps[0] will be level 0)
 */
module.exports = function createMipmap( filename, maxLevel, quality ) {
  return new Promise( ( resolve, reject ) => {
    const mipmaps = [];

    // kick everything off
    const suffix = filename.slice( -4 );
    if ( suffix === '.jpg' ) {
      loadJPEG();
    }
    else if ( suffix === '.png' ) {
      loadPNG();
    }
    else {
      throw new Error( 'unknown image type: ' + filename );
    }

    // Loads / decodes the initial JPEG image, and when done proceeds to the mipmapping
    function loadJPEG() {
      var imageData = jpeg.decode( fs.readFileSync( filename ) );

      mipmaps.push( {
        data: imageData.data,
        width: imageData.width,
        height: imageData.height
      } );

      startMipmapCreation();
    }

    // Loads / decodes the initial PNG image, and when done proceeds to the mipmapping
    function loadPNG() {
      var src = fs.createReadStream( filename );

      var basePNG = new pngjs.PNG( {
        // if we need a specific filter type, put it here
      } );

      basePNG.on( 'error', function( err ) {
        throw err;
      } );

      basePNG.on( 'parsed', function() {
        mipmaps.push( {
          data: basePNG.data,
          width: basePNG.width,
          height: basePNG.height
        } );

        startMipmapCreation();
      } );

      // pass the stream to pngjs
      src.pipe( basePNG );
    }

    /**
     * @param {Buffer} data - Should have 4*width*height elements
     * @param {number} width
     * @param {number} height
     * @param {number} quality - Out of 100
     * @param {function} callback - function( buffer )
     */
    function outputJPEG( data, width, height, quality, callback ) {
      var encodedOuput = jpeg.encode( {
        data: data,
        width: width,
        height: height
      }, quality );
      callback( encodedOuput.data );
    }

    /**
     * @param {Buffer} data - Should have 4*width*height elements
     * @param {number} width
     * @param {number} height
     * @param {function} callback - function( buffer )
     */
    function outputPNG( data, width, height, callback ) {
      // provides width/height so it is initialized with the correct-size buffer
      var png = new pngjs.PNG( {
        width: width,
        height: height
      } );

      // copy our image data into the pngjs.PNG's data buffer;
      data.copy( png.data, 0, 0, data.length );

      // will concatenate the buffers from the stream into one once it is finished
      var buffers = [];
      png.on( 'data', function( buffer ) {
        buffers.push( buffer );
      } );
      png.on( 'end', function() {
        var buffer = Buffer.concat( buffers );

        callback( buffer );
      } );
      png.on( 'error', function( err ) {
        throw err;
      } );

      // kick off the encoding of the PNG
      png.pack();
    }

    // called when our mipmap[0] level is loaded by decoding the main image (creates the mipmap levels)
    function startMipmapCreation() {
      // When reduced to 0, we'll be done with encoding (and can call our callback). Needed because they are asynchronous.
      var encodeCounter = 1;

      // Alpha detection on the level-0 image to see if we can swap jpg for png
      var hasAlpha = false;
      for ( var i = 3; i < mipmaps[ 0 ].data.length; i += 4 ) {
        if ( mipmaps[ 0 ].data[ i ] < 255 ) {
          hasAlpha = true;
          break;
        }
      }

      // called when all of encoding is complete
      function encodingComplete() {
        grunt.log.debug( 'mipmapped ' + filename + ( maxLevel >= 0 ? ' to level ' + maxLevel : '' ) + ' with quality: ' + quality );

        for ( var level = 0; level < mipmaps.length; level++ ) {
          // for now, make .url point to the smallest of the two (unless we have an alpha channel need)
          var usePNG = hasAlpha || mipmaps[ level ].jpgURL.length > mipmaps[ level ].pngURL.length;
          mipmaps[ level ].url = usePNG ? mipmaps[ level ].pngURL : mipmaps[ level ].jpgURL;
          mipmaps[ level ].buffer = usePNG ? mipmaps[ level ].pngBuffer : mipmaps[ level ].jpgBuffer;

          grunt.log.debug( 'level ' + level + ' (' + ( usePNG ? 'PNG' : 'JPG' ) + ' ' +
                           mipmaps[ level ].width + 'x' + mipmaps[ level ].height + ') base64: ' +
                           mipmaps[ level ].url.length + ' bytes ' );
        }

        resolve( mipmaps );
      }

      // kicks off asynchronous encoding for a specific level
      function encodeLevel( level ) {
        encodeCounter++;
        outputPNG( mipmaps[ level ].data, mipmaps[ level ].width, mipmaps[ level ].height, function( buffer ) {
          mipmaps[ level ].pngBuffer = buffer;
          mipmaps[ level ].pngURL = 'data:image/png;base64,' + buffer.toString( 'base64' );
          if ( --encodeCounter === 0 ) {
            encodingComplete();
          }
        } );

        // only encode JPEG if it has no alpha
        if ( !hasAlpha ) {
          encodeCounter++;
          outputJPEG( mipmaps[ level ].data, mipmaps[ level ].width, mipmaps[ level ].height, quality, function( buffer ) {
            mipmaps[ level ].jpgBuffer = buffer;
            mipmaps[ level ].jpgURL = 'data:image/jpeg;base64,' + buffer.toString( 'base64' );
            if ( --encodeCounter === 0 ) {
              encodingComplete();
            }
          } );
        }
      }

      // encode all levels, and compute rasters for levels 1-N
      encodeLevel( 0 );
      function finestMipmap() {
        return mipmaps[ mipmaps.length - 1 ];
      }

      // bail if we already have a 1x1 image, or if we reach the maxLevel (recall maxLevel===-1 means no maximum level)
      while ( ( mipmaps.length - 1 < maxLevel || maxLevel < 0 ) && ( finestMipmap().width > 1 || finestMipmap().height > 1 ) ) {
        var level = mipmaps.length;
        mipmaps.push( mipmapDownscale( finestMipmap(), function( width, height ) {
          return new Buffer( 4 * width * height );
        } ) );
        encodeLevel( level );
      }

      // just in case everything happened synchronously
      if ( --encodeCounter === 0 ) {
        encodingComplete();
      }
    }
  } );
};
<|endoftext|>


<commit_before>import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
import "babel-polyfill";

import LiveApi from '../src/LiveApi';
import ws from 'ws';

describe('read', function() {
	this.timeout(10000);
    let liveApi;
    let token;

    beforeEach(() => {
        liveApi = new LiveApi({ websocket: ws });
        token = '4yFDEnFI3EpnZ7M';
    });

    it('should be able to get account limit', ()=> {
    	expect( ()=>
    		liveApi.getAccountLimits()).to.not.throw()
    });

	it('should be able return account limits in a server response', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getAccountLimits()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_limits.account_balance).to.equal(10000);
  								done();
  							}, (err) => {
  								expect(err).to.equal(null);
  								done();
  							})
  						);

	});

    it('should be able to call the function getAccountSettings without error', () => {
    	expect( ()=>
    		liveApi.getAccountSettings()).to.not.throw()
    });

	it('should be able to return account settings from a api response', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getAccountSettings()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_settings.country).to.equal('Belgium');
  								done();
  							}, (err) => {
  								console.log('the error at getAccountSettings', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);

	});

	it('should be able to call getAccountStatus without an error', () => {
		expect( () =>
			liveApi.getAccountStatus()).to.not.throw();
	});

	it('should be able to get account status in a response from a server', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getAccountStatus()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_account_status).to.contains('active');
  								done();
  							}, (err) => {
  								console.log('the error at getAccountStatus', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);
	});

	it('should be able to call getSelfExclusion without an issue', () => {
		expect( () =>
			liveApi.getSelfExclusion()).to.not.throw();
	});

	it('should be able to getSelfExclusion in a response from a server', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getSelfExclusion()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_self_exclusion.max_balance).to.equal('10000');
  								done();
  							}, (err) => {
  								console.log('error raised at getSelfExclusion', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);
	});

	it('should be able to call logout function without issue', () => {
		expect( () =>
			liveApi.logOut()).to.not.throw();
	});

	it('should be able to sign user out of his account', async () => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.logOut()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								done();
  							}, (err) => {
  								console.log('error at logout', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);
	});

	it('it should be able to call getStatement function without an issue', ()=> {
		expect( () =>
			liveApi.getStatement({
			statement: 1,
  			description: 1,
  			limit: 100
		})).to.not.throw();
	});

	it('should return statement from response without an error', async () => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getStatement({
		 					statement: 1,
  							description: 1,
  							limit: 100
  						})
  						.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  							}, (err) => {
  								console.log('error at getStatement', err);
  								expect(err).to.equal(null);
  							})
  						);
	});

	it('should be able to call getProfitTable without an error', () => {
		expect( () =>
			liveApi.getProfitTable({description: 1})).to.not.throw();
	});

	it('should be able to return profitTable from the server', async () => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getProfitTable({description:1}).then(
		 					null, (err) => {
		 						expect(err).to.have.property('message');
		 						expect(err.message).to.contains('This account is unavailable.');
		 						done();
		 					})
		 				);

	});

	it('should be able to call subscribeToOpenContract without an issue', () => {
		expect( () =>
			liveApi.subscribeToOpenContract().to.not.throw());
	});

	it('should subscribeToOpenContract and return a server response', async (done) =>{
		return await liveApi.authorize(token).then(
			() => liveApi.subscribeToOpenContract().then(
				(response) => {
					expect(response.echo_req).to.have.property('subscribe');
					expect(response.echo_req.subscribe).to.equal(1);
					done();
				}, (err) =>{
					console.log('error at subscribeToOpenContract', err);
					expect(err).to.equal(null);
					done();
				})
			);
	})

});<commit_msg>Fix logout test<commit_after>import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
import "babel-polyfill";

import LiveApi from '../src/LiveApi';
import ws from 'ws';

describe('read', function() {
	this.timeout(10000);
    let liveApi;
    let token;

    beforeEach(() => {
        liveApi = new LiveApi({ websocket: ws });
        token = '4yFDEnFI3EpnZ7M';
    });

    it('should be able to get account limit', ()=> {
    	expect( ()=>
    		liveApi.getAccountLimits()).to.not.throw()
    });

	it('should be able return account limits in a server response', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getAccountLimits()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_limits.account_balance).to.equal(10000);
  								done();
  							}, (err) => {
  								expect(err).to.equal(null);
  								done();
  							})
  						);

	});

    it('should be able to call the function getAccountSettings without error', () => {
    	expect( ()=>
    		liveApi.getAccountSettings()).to.not.throw()
    });

	it('should be able to return account settings from a api response', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getAccountSettings()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_settings.country).to.equal('Belgium');
  								done();
  							}, (err) => {
  								console.log('the error at getAccountSettings', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);

	});

	it('should be able to call getAccountStatus without an error', () => {
		expect( () =>
			liveApi.getAccountStatus()).to.not.throw();
	});

	it('should be able to get account status in a response from a server', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getAccountStatus()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_account_status).to.contains('active');
  								done();
  							}, (err) => {
  								console.log('the error at getAccountStatus', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);
	});

	it('should be able to call getSelfExclusion without an issue', () => {
		expect( () =>
			liveApi.getSelfExclusion()).to.not.throw();
	});

	it('should be able to getSelfExclusion in a response from a server', async (done) => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getSelfExclusion()
		 				.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  								expect(response.get_self_exclusion.max_balance).to.equal('10000');
  								done();
  							}, (err) => {
  								console.log('error raised at getSelfExclusion', err);
  								expect(err).to.equal(null);
  								done();
  							})
  						);
	});

	it('should be able to call logout function', () => {
		expect(() =>
			liveApi.logOut()
		).to.not.throw();
	});

	it('should be able to sign user out of his account', async () => {
		await liveApi.authorize(token);
		const response = await liveApi.logOut();
		expect(response).to.not.have.property('error');
	});

	it('it should be able to call getStatement function without an issue', ()=> {
		expect( () =>
			liveApi.getStatement({
			statement: 1,
  			description: 1,
  			limit: 100
		})).to.not.throw();
	});

	it('should return statement from response without an error', async () => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getStatement({
		 					statement: 1,
  							description: 1,
  							limit: 100
  						})
  						.then(
  							(response) => {
  								expect(response).to.not.have.property('error');
  							}, (err) => {
  								console.log('error at getStatement', err);
  								expect(err).to.equal(null);
  							})
  						);
	});

	it('should be able to call getProfitTable without an error', () => {
		expect( () =>
			liveApi.getProfitTable({description: 1})).to.not.throw();
	});

	it('should be able to return profitTable from the server', async () => {

		return await liveApi.authorize(token).then(
		 				()=> liveApi.getProfitTable({description:1}).then(
		 					null, (err) => {
		 						expect(err).to.have.property('message');
		 						expect(err.message).to.contains('This account is unavailable.');
		 						done();
		 					})
		 				);

	});

	it('should be able to call subscribeToOpenContract without an issue', () => {
		expect( () =>
			liveApi.subscribeToOpenContract().to.not.throw());
	});

	it('should subscribeToOpenContract and return a server response', async (done) =>{
		return await liveApi.authorize(token).then(
			() => liveApi.subscribeToOpenContract().then(
				(response) => {
					expect(response.echo_req).to.have.property('subscribe');
					expect(response.echo_req.subscribe).to.equal(1);
					done();
				}, (err) =>{
					console.log('error at subscribeToOpenContract', err);
					expect(err).to.equal(null);
					done();
				})
			);
	})

});
<|endoftext|>


<commit_before>#!/usr/bin/env node

process.stdin.resume();
process.stdin.setEncoding('utf8');

var wrench = require('wrench');
var fs = require('fs');
var path = require('path');
var json = require('json');

var argv = require('optimist').argv;

var command = argv._[0];
var arg1 = argv._[1];
var force = argv.force ? true : false;
var sampleDirName = 'ncombo-samples';

var parsePackageFile = function(moduleDir) {
	var packageFile = moduleDir + '/package.json';
	try {
		if(fs.existsSync(packageFile)) {
			return json.parse(fs.readFileSync(packageFile, 'utf8'));
		}
	} catch(e) {}

	return {};
}

var errorMessage = function(message) {
	console.log('\033[0;31m[Error]\033[0m ' + message);
}

var successMessage = function(message) {
	console.log('\033[0;32m[Success]\033[0m ' + message);
}

var warningMessage = function(message) {
	console.log('\033[0;33m[Warning]\033[0m ' + message);
}

var showCorrectUsage = function() {
	console.log('Usage: ncombo [options] [command]\n');
	console.log('Options:');
	console.log("  -v                Get the version number of the current nCombo installation");
	console.log('  --help            Get info on how to use this command');
	console.log('  --force           Force all necessary directory modifications without prompts');
	console.log();
	console.log('Commands:');
	console.log('  create <appname>  Create a new app <appname> within the working directory');
	console.log('  samples           Create an ncombo-samples directory');
}

var failedToRemoveDirMessage = function(dirPath) {
	errorMessage('Failed to remove existing directory at ' + dirPath + '. This directory may be used by another program or you may not have the permission to remove it.');
}

var failedToCreateMessage = function() {
	errorMessage('Failed to create necessary files. Please check your permissions and try again.');
}

var prompt = function(message, callback) {
	process.stdout.write(message + ' ');
	process.stdin.on('data', function inputHandler(text) {
		process.stdin.removeListener('data', inputHandler);
		callback(text)
	});
}

var prompConfirm = function(message, callback) {
	prompt(message, function(data) {
		data = data.toLowerCase().replace(/[\r\n]/g, '');
		callback(data == 'y' || data == 'yes');
	});
}

var copyDirRecursive = function(src, dest) {
	try {
		wrench.copyDirSyncRecursive(src, dest);
		return true;
	} catch(e) {
		failedToCreateMessage();
	}
	return false;
}

var rmdirRecursive = function(dirname) {
	try {
		wrench.rmdirSyncRecursive(dirname);
		return true;
	} catch(e) {
		failedToRemoveDirMessage(dirname);
	}
	return false;
}

var createFrameworkDir = function(destDir, callback) {
	var nComboSrcDir = __dirname + '/..';
	var progressMessage = 'Installing nCombo modules... This may take a while.';
	var finishedMessage = 'Done';
	var success = true;
	var subModulesDir = destDir + '/node_modules';
	var mainModulesDir = destDir + '/../';
	var coreModulesDir = destDir + '/';

	var moveModules = function() {
		var nodeModules = fs.readdirSync(subModulesDir);
		var coreModules, coreModuleDir;
		var curFile, moduleDest;
		var i, j;
		for(i in nodeModules) {
			curFile = subModulesDir + '/' + nodeModules[i];
			if(nodeModules[i] == 'ncombo') {
				coreModules = fs.readdirSync(curFile);
				for(j in coreModules) {
					if(coreModules[j] != 'index.js') {
						coreModuleDir = curFile + '/' + coreModules[j];
						copyDirRecursive(coreModuleDir, coreModulesDir + coreModules[j]);
						wrench.rmdirSyncRecursive(coreModuleDir);
					}
				}
			} else if(fs.statSync(curFile).isDirectory()) {
				moduleDest = mainModulesDir + nodeModules[i];
				if(fs.existsSync(moduleDest)) {
					wrench.rmdirSyncRecursive(moduleDest);
				}
				copyDirRecursive(curFile, moduleDest);
				wrench.rmdirSyncRecursive(curFile);
			}
		}
	}

	var proceed = function(confirm) {
		if(confirm) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(nComboSrcDir, destDir);
			moveModules();
			console.log(finishedMessage);
		}
		callback(success);
	}

	if(fs.existsSync(destDir)) {
		var srcPkg = parsePackageFile(nComboSrcDir);
		var destPkg = parsePackageFile(destDir);

		if(srcPkg.version == destPkg.version) {
			moveModules();
			callback(success);
		} else if(force) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(nComboSrcDir, destDir);
			moveModules();
			console.log(finishedMessage);
			callback(success);
		} else {
			prompConfirm('A different version of the nCombo framework directory already exists at ' + nComboDestDir + '. Overwrite it (y/n)?', proceed);
		}
	} else {
		console.log(progressMessage);
		success = copyDirRecursive(nComboSrcDir, destDir);
		moveModules();
		console.log(finishedMessage);
		callback(success);
	}
}

var createSamplesDir = function(destDir, callback) {
	var samplesSrcPath = __dirname + '/../samples';
	var progressMessage = 'Setting up sample apps...';
	var finishedMessage = 'Done';
	var success = true;
	var proceed = function(confirm) {
		if(confirm) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(samplesSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			errorMessage('Skipped samples setup process');
			callback(false);
		}
	}

	if(fs.existsSync(destDir)) {
		if(force) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(samplesSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			prompConfirm('A directory already exists at ' + destDir + '. Overwrite it (y/n)?', proceed);
		}
	} else {
		console.log(progressMessage);
		success = copyDirRecursive(samplesSrcPath, destDir);
		console.log(finishedMessage);
		callback(success);
	}
}

var createAppDir = function(destDir, callback) {
	var appSrcPath = __dirname + '/../app';
	var progressMessage = 'Setting up app structure...';
	var finishedMessage = 'Done';
	var success = true;
	var proceed = function(confirm) {
		if(confirm) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(appSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			errorMessage('Skipped app structure setup process');
			callback(false);
		}
	}

	if(fs.existsSync(destDir)) {
		if(force) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(appSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			prompConfirm('A directory already exists at ' + destDir + '. Overwrite it (y/n)?', proceed);
		}
	} else {
		console.log(progressMessage);
		success = copyDirRecursive(appSrcPath, destDir);
		console.log(finishedMessage);
		callback(success);
	}
}

if(argv.help) {
	showCorrectUsage();
	process.exit();
}

if(argv.v) {
	var nComboDir = __dirname + '/../../ncombo';
	var nComboPkg = parsePackageFile(nComboDir);
	console.log('v' + nComboPkg.version);
	process.exit();
}

var wd = process.cwd();

var nodeModulesDir = wd + '/node_modules';
var nComboDestDir = nodeModulesDir + '/ncombo';

if(!fs.existsSync(nodeModulesDir)) {
	fs.mkdirSync(nodeModulesDir);
}

if(command == 'create' || command == 'samples') {
	var begin = function(confirm) {
		if(confirm) {
			createFrameworkDir(nComboDestDir, function(frameworkSuccess) {
				if(frameworkSuccess) {
					if(command == 'samples') {
						var samplesDestDir = path.normalize(wd + '/' + sampleDirName);
						createSamplesDir(samplesDestDir, function(samplesSuccess) {
							if(samplesSuccess) {
								successMessage("Install process is complete. Run 'node " + sampleDirName + "/memo/server' to launch the memo sample app. Access at http://localhost:8000/");
							}
							process.exit();
						});
					} else if(command == 'create') {
						if(arg1) {
							var appDestDir = path.normalize(wd + '/' + arg1);
							createAppDir(appDestDir, function(appSuccess) {
								if(appSuccess) {
									successMessage("Install process is complete. Run 'node " + arg1 + "/server' to launch. Access at http://localhost:8000/");
								}
								process.exit();
							});
						} else {
							successMessage('nCombo framework core has been installed. nCombo apps which are placed within the ' + wd + ' directory (including subdirectories) will use this framework core.');
							process.exit();
						}
					}
				} else {
					process.exit();
				}
			});
		} else {
			errorMessage("nCombo installation was cancelled");
			process.exit();
		}
	}

	if(force) {
		begin(true);
	} else {
		prompConfirm('You are about to install nCombo. Please note that as part of the installation process, new modules will be added inside the ' + nodeModulesDir + ' directory - Existing modules may be overwitten. Would you like to continue (y/n)?', begin);
	}
} else {
	errorMessage("'" + command + "' is not a valid nCombo command");
	showCorrectUsage();
	process.exit();
}
<commit_msg>Adjusted text in cli<commit_after>#!/usr/bin/env node

process.stdin.resume();
process.stdin.setEncoding('utf8');

var wrench = require('wrench');
var fs = require('fs');
var path = require('path');
var json = require('json');

var argv = require('optimist').argv;

var command = argv._[0];
var arg1 = argv._[1];
var force = argv.force ? true : false;
var sampleDirName = 'ncombo-samples';

var parsePackageFile = function(moduleDir) {
	var packageFile = moduleDir + '/package.json';
	try {
		if(fs.existsSync(packageFile)) {
			return json.parse(fs.readFileSync(packageFile, 'utf8'));
		}
	} catch(e) {}

	return {};
}

var errorMessage = function(message) {
	console.log('\033[0;31m[Error]\033[0m ' + message);
}

var successMessage = function(message) {
	console.log('\033[0;32m[Success]\033[0m ' + message);
}

var warningMessage = function(message) {
	console.log('\033[0;33m[Warning]\033[0m ' + message);
}

var showCorrectUsage = function() {
	console.log('Usage: ncombo [options] [command]\n');
	console.log('Options:');
	console.log("  -v                Get the version number of the current nCombo installation");
	console.log('  --help            Get info on how to use this command');
	console.log('  --force           Force all necessary directory modifications without prompts');
	console.log();
	console.log('Commands:');
	console.log('  create <appname>  Create a new app <appname> within the working directory');
	console.log('  samples           Create an ncombo-samples directory');
}

var failedToRemoveDirMessage = function(dirPath) {
	errorMessage('Failed to remove existing directory at ' + dirPath + '. This directory may be used by another program or you may not have the permission to remove it.');
}

var failedToCreateMessage = function() {
	errorMessage('Failed to create necessary files. Please check your permissions and try again.');
}

var prompt = function(message, callback) {
	process.stdout.write(message + ' ');
	process.stdin.on('data', function inputHandler(text) {
		process.stdin.removeListener('data', inputHandler);
		callback(text)
	});
}

var prompConfirm = function(message, callback) {
	prompt(message, function(data) {
		data = data.toLowerCase().replace(/[\r\n]/g, '');
		callback(data == 'y' || data == 'yes');
	});
}

var copyDirRecursive = function(src, dest) {
	try {
		wrench.copyDirSyncRecursive(src, dest);
		return true;
	} catch(e) {
		failedToCreateMessage();
	}
	return false;
}

var rmdirRecursive = function(dirname) {
	try {
		wrench.rmdirSyncRecursive(dirname);
		return true;
	} catch(e) {
		failedToRemoveDirMessage(dirname);
	}
	return false;
}

var createFrameworkDir = function(destDir, callback) {
	var nComboSrcDir = __dirname + '/..';
	var progressMessage = 'Installing nCombo modules... This may take a while.';
	var finishedMessage = 'Done';
	var success = true;
	var subModulesDir = destDir + '/node_modules';
	var mainModulesDir = destDir + '/../';
	var coreModulesDir = destDir + '/';

	var moveModules = function() {
		var nodeModules = fs.readdirSync(subModulesDir);
		var coreModules, coreModuleDir;
		var curFile, moduleDest;
		var i, j;
		for(i in nodeModules) {
			curFile = subModulesDir + '/' + nodeModules[i];
			if(nodeModules[i] == 'ncombo') {
				coreModules = fs.readdirSync(curFile);
				for(j in coreModules) {
					if(coreModules[j] != 'index.js') {
						coreModuleDir = curFile + '/' + coreModules[j];
						copyDirRecursive(coreModuleDir, coreModulesDir + coreModules[j]);
						wrench.rmdirSyncRecursive(coreModuleDir);
					}
				}
			} else if(fs.statSync(curFile).isDirectory()) {
				moduleDest = mainModulesDir + nodeModules[i];
				if(fs.existsSync(moduleDest)) {
					wrench.rmdirSyncRecursive(moduleDest);
				}
				copyDirRecursive(curFile, moduleDest);
				wrench.rmdirSyncRecursive(curFile);
			}
		}
	}

	var proceed = function(confirm) {
		if(confirm) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(nComboSrcDir, destDir);
			moveModules();
			console.log(finishedMessage);
		}
		callback(success);
	}

	if(fs.existsSync(destDir)) {
		var srcPkg = parsePackageFile(nComboSrcDir);
		var destPkg = parsePackageFile(destDir);

		if(srcPkg.version == destPkg.version) {
			moveModules();
			callback(success);
		} else if(force) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(nComboSrcDir, destDir);
			moveModules();
			console.log(finishedMessage);
			callback(success);
		} else {
			prompConfirm('A different version of the nCombo framework directory already exists at ' + nComboDestDir + '. Overwrite it (y/n)?', proceed);
		}
	} else {
		console.log(progressMessage);
		success = copyDirRecursive(nComboSrcDir, destDir);
		moveModules();
		console.log(finishedMessage);
		callback(success);
	}
}

var createSamplesDir = function(destDir, callback) {
	var samplesSrcPath = __dirname + '/../samples';
	var progressMessage = 'Setting up sample apps...';
	var finishedMessage = 'Done';
	var success = true;
	var proceed = function(confirm) {
		if(confirm) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(samplesSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			errorMessage('Skipped samples setup process');
			callback(false);
		}
	}

	if(fs.existsSync(destDir)) {
		if(force) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(samplesSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			prompConfirm('A directory already exists at ' + destDir + '. Overwrite it (y/n)?', proceed);
		}
	} else {
		console.log(progressMessage);
		success = copyDirRecursive(samplesSrcPath, destDir);
		console.log(finishedMessage);
		callback(success);
	}
}

var createAppDir = function(destDir, callback) {
	var appSrcPath = __dirname + '/../app';
	var progressMessage = 'Setting up app structure...';
	var finishedMessage = 'Done';
	var success = true;
	var proceed = function(confirm) {
		if(confirm) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(appSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			errorMessage('Skipped app structure setup process');
			callback(false);
		}
	}

	if(fs.existsSync(destDir)) {
		if(force) {
			console.log(progressMessage);
			success = rmdirRecursive(destDir) && copyDirRecursive(appSrcPath, destDir);
			console.log(finishedMessage);
			callback(success);
		} else {
			prompConfirm('A directory already exists at ' + destDir + '. Overwrite it (y/n)?', proceed);
		}
	} else {
		console.log(progressMessage);
		success = copyDirRecursive(appSrcPath, destDir);
		console.log(finishedMessage);
		callback(success);
	}
}

if(argv.help) {
	showCorrectUsage();
	process.exit();
}

if(argv.v) {
	var nComboDir = __dirname + '/../../ncombo';
	var nComboPkg = parsePackageFile(nComboDir);
	console.log('v' + nComboPkg.version);
	process.exit();
}

var wd = process.cwd();

var nodeModulesDir = wd + '/node_modules';
var nComboDestDir = nodeModulesDir + '/ncombo';

if(!fs.existsSync(nodeModulesDir)) {
	fs.mkdirSync(nodeModulesDir);
}

if(command == 'create' || command == 'samples') {
	var begin = function(confirm) {
		if(confirm) {
			createFrameworkDir(nComboDestDir, function(frameworkSuccess) {
				if(frameworkSuccess) {
					if(command == 'samples') {
						var samplesDestDir = path.normalize(wd + '/' + sampleDirName);
						createSamplesDir(samplesDestDir, function(samplesSuccess) {
							if(samplesSuccess) {
								successMessage("Install process is complete. Run 'node " + sampleDirName + "/memo/server.node' to launch the memo sample app. Access at http://localhost:8000/");
							}
							process.exit();
						});
					} else if(command == 'create') {
						if(arg1) {
							var appDestDir = path.normalize(wd + '/' + arg1);
							createAppDir(appDestDir, function(appSuccess) {
								if(appSuccess) {
									successMessage("Install process is complete. Run 'node " + arg1 + "/server.node' to launch. Access at http://localhost:8000/");
								}
								process.exit();
							});
						} else {
							successMessage('nCombo framework core has been installed. nCombo apps which are placed within the ' + wd + ' directory (including subdirectories) will use this framework core.');
							process.exit();
						}
					}
				} else {
					process.exit();
				}
			});
		} else {
			errorMessage("nCombo installation was cancelled");
			process.exit();
		}
	}

	if(force) {
		begin(true);
	} else {
		prompConfirm('You are about to install nCombo. Please note that as part of the installation process, new modules will be added inside the ' + nodeModulesDir + ' directory - Existing modules may be overwitten. Would you like to continue (y/n)?', begin);
	}
} else {
	errorMessage("'" + command + "' is not a valid nCombo command");
	showCorrectUsage();
	process.exit();
}
<|endoftext|>


<commit_before>var DefaultAttributeLocationBinding = require('./ProgramAttributeLocationBinding');

//TODO: this is true in 99% of cases, might be implementation specific
var NUM_VERTEX_ATTRIBUTES_MAX = 16;

var STR_ERROR_UNIFORM_UNDEFINED = 'Uniform "%s" is not defined.';
var STR_ERROR_WRONG_NUM_ARGS = 'Wrong number of arguments.';
var STR_ERROR_INVALID_UNIFORM_TYPE = 'Invalid uniform type "%s".';
var STR_ERROR_ATTRIBUTE_BINDING_UNDEFINED = 'Attribute "%s" is not present in program.';

function Program(context, vertSrc, fragSrc, attributeLocationBinding){
    var gl = this._gl = context.getGL();

    //TODO: creating program once like Pex or on every load like Foam
    //If we don't do this in init then bind() can be invalid and it should throw
    this._handle     = gl.createProgram();
    this._attributes = {};
    this._uniforms   = {};
    if(vertSrc){
        this.update(vertSrc, fragSrc, attributeLocationBinding);
    }
}

Program.prototype.getHandle = function(){
    return this._handle;
};

Program.prototype._bindInternal = function(){

    this._gl.useProgram(this._handle);
};

/**
 * updates ahders sources and links the program
 * @param  {String} vertSrc                 - vert shader source (or combined vert/fragShader)
 * @param  {String} [fragSrc]               - frag shader source
 * @param  {String} [attributeLocationBinding] - attribute locations map { 0: 'aPositon', 1: 'aNormal', 2: 'aColor' }
 */
Program.prototype.update = function(vertSrc, fragSrc, attributeLocationBinding){
    var gl = this._gl;
    var program = this._handle;

    var vertShader = this._compileSource(gl.VERTEX_SHADER, vertSrc);
    var fragShader = this._compileSource(gl.FRAGMENT_SHADER, fragSrc);

    gl.attachShader(program, vertShader);
    gl.attachShader(program, fragShader);

    for(var location = 0; location < NUM_VERTEX_ATTRIBUTES_MAX; location++){
        var attributeName = (attributeLocationBinding && attributeLocationBinding[location]) || DefaultAttributeLocationBinding[location];
        gl.bindAttribLocation(program, location, attributeName);
    }

    gl.linkProgram(program);

    if(!gl.getProgramParameter(program, gl.LINK_STATUS)){
        throw new Error('PROGRAM: ' + gl.getProgramInfoLog(program));
    }

    //Mark for deletion, they are not actually deleted until you call deleteProgram() in dispose()
    gl.deleteShader(vertShader);
    gl.deleteShader(fragShader);

    this._updateUniforms();
    this._updateAttributes();

    for(var location in attributeLocationBinding){
        var attributeName = attributeLocationBinding[location];
        if(this._attributes[attributeName] === undefined){
            throw new Error(STR_ERROR_ATTRIBUTE_BINDING_UNDEFINED.replace('%s', attributeName));
        }
    }
};

Program.prototype._updateUniforms = function(){
    var gl = this._gl;
    var program     = this._handle;
    var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
    var uniforms    = this._uniforms = {};

    for(var i = 0, info, name; i < numUniforms; ++i){
        info = gl.getActiveUniform(program, i);
        name = info.name;
        uniforms[name] = {
            type : info.type,
            location : gl.getUniformLocation(program, name)
        };
    }
};

Program.prototype._updateAttributes = function(){
    var gl = this._gl;
    var program       = this._handle;
    var numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
    var attributes    = this._attributes = {};

    for(var i = 0, info, name; i < numAttributes; ++i){
        info = gl.getActiveAttrib(program, i);
        name = info.name;
        attributes[name] = {
            type : info.type,
            location : gl.getAttribLocation(program, name)
        }
    }
};

Program.prototype._compileSource = function(type, src){
    var gl = this._gl;
    var shader = gl.createShader(type);

    gl.shaderSource(shader, src + '\n');
    gl.compileShader(shader);
    if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
        throw new Error((type === gl.VERTEX_SHADER ? 'Vertex ' : 'Fragment ') + 'shader: ' + gl.getShaderInfoLog(shader));
    }
    return shader;
};

Program.prototype.setUniform = function(name, args){
    var uniform = this._uniforms[name];
    if(uniform === undefined){
        throw new Error(STR_ERROR_UNIFORM_UNDEFINED.replace('%s', name));
    }
    var type     = uniform.type;
    var location = uniform.location;
    var numArgs  = arguments.length - 1;
    var gl       = this._gl;

    switch(type){
        case gl.INT:
        case gl.BOOL:
            if(numArgs != 1){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            gl.uniform1i(location, arguments[1]);
            break;
        case gl.SAMPLER_2D:
        case gl.SAMPLER_CUBE:
            if(numArgs != 1){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            gl.uniform1i(location, arguments[1]);
            //but could also be texture
            break;
        case gl.FLOAT:
            if(numArgs != 1){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            gl.uniform1f(location, arguments[1]);
            break;
        case gl.FLOAT_VEC2:
            if(numArgs != 1 && numArgs != 2){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            if(numArgs == 1){
                gl.uniform2fv(location, arguments[1]);
            }else{
                gl.uniform2f(location, arguments[1], arguments[2]);
            }
            break;
        case gl.FLOAT_VEC3:
            if(numArgs != 1 && numArgs != 3){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            if(numArgs == 1){
                gl.uniform3fv(location, arguments[1]);
            }else{
                gl.uniform3f(location, arguments[1], arguments[2], arguments[3]);
            }
            break;
        case gl.FLOAT_VEC4:
            if(numArgs != 1 && numArgs != 4){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            if(numArgs == 1){
                gl.uniform4fv(location, arguments[1]);
            }else{
                gl.uniform4f(location, arguments[1], arguments[2], arguments[3], arguments[4]);
            }
            break;
        case gl.FLOAT_MAT2:
            if(numArgs != 1){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            gl.uniformMatrix2fv(location, false, arguments[1]);
            break;
        case gl.FLOAT_MAT3:
            if(numArgs != 1){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            gl.uniformMatrix3fv(location, false, arguments[1]);
            break;
        case gl.FLOAT_MAT4:
            if(numArgs != 1){
                throw new Error(STR_ERROR_WRONG_NUM_ARGS);
            }
            gl.uniformMatrix4fv(location, false, arguments[1]);
            break;
        default :
            throw new Error(STR_ERROR_INVALID_UNIFORM_TYPE.replace('%s', type));
            break;
    }
};

Program.prototype.hasUniform = function(name){
    return this._uniforms[name] !== undefined;
};

Program.prototype.dispose = function(){
    if(!this._handle){
        return this;
    }
    this._gl.deleteProgram(this._handle);
    this._handle = null;
    return this;
};

module.exports = Program;
<commit_msg>06-pex-ctx-00 Program replaced switch and arguments.length check with uniform function lookup per type<commit_after>var DefaultAttributeLocationBinding = require('./ProgramAttributeLocationBinding');

//TODO: this is true in 99% of cases, might be implementation specific
var NUM_VERTEX_ATTRIBUTES_MAX = 16;

var STR_ERROR_UNIFORM_UNDEFINED = 'Uniform "%s" is not defined.';
var STR_ERROR_WRONG_NUM_ARGS = 'Wrong number of arguments.';
var STR_ERROR_INVALID_UNIFORM_TYPE = 'Unsupported uniform type "%s".';
var STR_ERROR_ATTRIBUTE_BINDING_UNDEFINED = 'Attribute "%s" is not present in program.';

function Program(context, vertSrc, fragSrc, attributeLocationBinding){
    var gl = this._gl = context.getGL();

    this._handle           = gl.createProgram();
    this._attributes       = {};
    this._uniforms         = {};
    this._uniformSetterMap = {};
    if(vertSrc){
        this.update(vertSrc, fragSrc, attributeLocationBinding);
    }
}

Program.prototype.getHandle = function(){
    return this._handle;
};

Program.prototype._bindInternal = function(){
    this._gl.useProgram(this._handle);
};

/**
 * updates shaders sources and links the program
 * @param  {String} vertSrc                 - vert shader source (or combined vert/fragShader)
 * @param  {String} [fragSrc]               - frag shader source
 * @param  {String} [attributeLocationBinding] - attribute locations map { 0: 'aPositon', 1: 'aNormal', 2: 'aColor' }
 */
Program.prototype.update = function(vertSrc, fragSrc, attributeLocationBinding){
    var gl = this._gl;
    var program = this._handle;

    var vertShader = this._compileSource(gl.VERTEX_SHADER, vertSrc);
    var fragShader = this._compileSource(gl.FRAGMENT_SHADER, fragSrc);

    gl.attachShader(program, vertShader);
    gl.attachShader(program, fragShader);

    for(var location = 0; location < NUM_VERTEX_ATTRIBUTES_MAX; location++){
        var attributeName = (attributeLocationBinding && attributeLocationBinding[location]) || DefaultAttributeLocationBinding[location];
        gl.bindAttribLocation(program, location, attributeName);
    }

    gl.linkProgram(program);

    if(!gl.getProgramParameter(program, gl.LINK_STATUS)){
        throw new Error('PROGRAM: ' + gl.getProgramInfoLog(program));
    }

    //Mark for deletion, they are not actually deleted until you call deleteProgram() in dispose()
    gl.deleteShader(vertShader);
    gl.deleteShader(fragShader);

    this._updateUniforms();
    this._updateAttributes();

    for(var location in attributeLocationBinding){
        var attributeName = attributeLocationBinding[location];
        if(this._attributes[attributeName] === undefined){
            throw new Error(STR_ERROR_ATTRIBUTE_BINDING_UNDEFINED.replace('%s', attributeName));
        }
    }

    this._updateUniformSetterMap();
};

Program.prototype._updateUniforms = function(){
    var gl = this._gl;
    var program     = this._handle;
    var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
    var uniforms    = this._uniforms = {};

    for(var i = 0, info, name; i < numUniforms; ++i){
        info = gl.getActiveUniform(program, i);
        name = info.name;
        uniforms[name] = {
            type : info.type,
            location : gl.getUniformLocation(program, name)
        };
    }
};

Program.prototype._updateAttributes = function(){
    var gl = this._gl;
    var program       = this._handle;
    var numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
    var attributes    = this._attributes = {};

    for(var i = 0, info, name; i < numAttributes; ++i){
        info = gl.getActiveAttrib(program, i);
        name = info.name;
        attributes[name] = {
            type : info.type,
            location : gl.getAttribLocation(program, name)
        }
    }
};

Program.prototype._compileSource = function(type, src){
    var gl = this._gl;
    var shader = gl.createShader(type);

    gl.shaderSource(shader, src + '\n');
    gl.compileShader(shader);
    if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
        throw new Error((type === gl.VERTEX_SHADER ? 'Vertex ' : 'Fragment ') + 'shader: ' + gl.getShaderInfoLog(shader));
    }
    return shader;
};

Program.prototype._updateUniformSetterMap = function(){
    var gl = this._gl;

    this._uniformSetterMap = {};
    for(var entry in this._uniforms){
        var type = this._uniforms[entry].type;
        if(this._uniformSetterMap[type] === undefined){
            switch (type){
                case gl.INT:
                case gl.BOOL:
                case gl.SAMPLER_2D:
                case gl.SAMPLER_CUBE:
                    this._uniformSetterMap[gl.INT] = this._uniformSetterMap[gl.INT] || function(location,x,y,z,w){
                        if(x === undefined || y !== undefined){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        gl.uniform1i(location,x);
                    };
                    this._uniformSetterMap[type] = this._uniformSetterMap[gl.INT];
                    break;
                case gl.FLOAT:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || y !== undefined){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        gl.uniform1f(location,x);
                    };
                    break;
                case gl.FLOAT_VEC2:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || z !== undefined){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        if(y === undefined){
                            gl.uniform2fv(location,x);
                        }
                        else {
                            gl.uniform2f(location,x,y);
                        }
                    };
                    break;
                case gl.FLOAT_VEC3:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || w !== undefined || (y !== undefined && z === undefined)){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        if(y === undefined){
                            gl.uniform3fv(location,x);
                        }
                        else {
                            gl.uniform3f(location,x,y,z);
                        }
                    };
                    break;
                case gl.FLOAT_VEC4:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || (y !== undefined && z === undefined) || (z !== undefined && w === undefined)){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        if(y === undefined){
                            gl.uniform4fv(location,x);
                        }
                        else {
                            gl.uniform4f(location,x,y,z,w);
                        }
                    };
                    break;
                case gl.FLOAT_MAT2:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || y !== undefined){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        gl.uniformMatrix2fv(location,false,x);
                    };
                    break;
                case gl.FLOAT_MAT3:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || y !== undefined){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        gl.uniformMatrix3fv(location,false,x);
                    };
                    break;
                case gl.FLOAT_MAT4:
                    this._uniformSetterMap[type] = function(location,x,y,z,w){
                        if(x === undefined || y !== undefined){
                            throw new Error(STR_ERROR_WRONG_NUM_ARGS);
                        }
                        gl.uniformMatrix4fv(location,false,x);
                    };
                    break;
                default:
                    throw new Error(STR_ERROR_INVALID_UNIFORM_TYPE.replace('%s',type));
                    break;
            }
        }
    }

};

Program.prototype.setUniform = function(name, x, y, z, w){
    var uniform = this._uniforms[name];
    if(uniform === undefined){
        throw new Error(STR_ERROR_UNIFORM_UNDEFINED.replace('%s', name));
    }
    this._uniformSetterMap[uniform.type](uniform.location,x,y,z,w);
};

Program.prototype.hasUniform = function(name){
    return this._uniforms[name] !== undefined;
};

Program.prototype.dispose = function(){
    if(!this._handle){
        return this;
    }
    this._gl.deleteProgram(this._handle);
    this._handle = null;
    return this;
};

module.exports = Program;
<|endoftext|>