Commit aa54d3d8 authored by chriseth's avatar chriseth Committed by GitHub

Merge pull request #74 from redsquirrel/unit-testing

Instrumenting project for unit testing
parents d87b3455 ad041b7b
language: node_js language: node_js
node_js: node_js:
- stable - stable
script: npm run lint && npm run build script: npm run lint && npm run test && npm run build
addons: addons:
sauce_connect: sauce_connect:
username: "chriseth" username: "chriseth"
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"description": "Minimalistic browser-based Solidity IDE", "description": "Minimalistic browser-based Solidity IDE",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "node test/index.js",
"build": "mkdir -p build; browserify src/index.js -o build/app.js", "build": "mkdir -p build; browserify src/index.js -o build/app.js",
"lint": "semistandard" "lint": "semistandard"
}, },
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"jquery": "^2.2.0", "jquery": "^2.2.0",
"js-base64": "^2.1.9", "js-base64": "^2.1.9",
"semistandard": "^7.0.0", "semistandard": "^7.0.0",
"tape": "^4.5.1",
"web3": "^0.15.3", "web3": "^0.15.3",
"webworkify": "^1.2.1" "webworkify": "^1.2.1"
}, },
......
...@@ -3,12 +3,16 @@ ...@@ -3,12 +3,16 @@
var $ = require('jquery'); var $ = require('jquery');
var utils = require('./app/utils'); var utils = require('./app/utils');
var queryParams = require('./app/query-params'); var QueryParams = require('./app/query-params');
var gistHandler = require('./app/gist-handler'); var queryParams = new QueryParams();
var GistHandler = require('./app/gist-handler');
var gistHandler = new GistHandler();
var StorageHandler = require('./app/storage-handler'); var StorageHandler = require('./app/storage-handler');
var Editor = require('./app/editor'); var Editor = require('./app/editor');
var Renderer = require('./app/renderer');
var Compiler = require('./app/compiler'); var Compiler = require('./app/compiler');
var ExecutionContext = require('./app/execution-context');
// The event listener needs to be registered as early as possible, because the // The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event. // parent will send the message upon the "load" event.
...@@ -61,7 +65,7 @@ var run = function () { ...@@ -61,7 +65,7 @@ var run = function () {
// ------------------ gist load ---------------- // ------------------ gist load ----------------
var loadingFromGist = gistHandler.handleLoad(function (gistId) { var loadingFromGist = gistHandler.handleLoad(queryParams.get(), function (gistId) {
$.ajax({ $.ajax({
url: 'https://api.github.com/gists/' + gistId, url: 'https://api.github.com/gists/' + gistId,
jsonp: 'callback', jsonp: 'callback',
...@@ -421,7 +425,10 @@ var run = function () { ...@@ -421,7 +425,10 @@ var run = function () {
return $.getJSON('https://api.github.com/repos/' + root + '/contents/' + path, cb); return $.getJSON('https://api.github.com/repos/' + root + '/contents/' + path, cb);
} }
var compiler = new Compiler(editor, handleGithubCall, $('#output'), getHidingRHP, updateFiles); var executionContext = new ExecutionContext();
var renderer = new Renderer(editor, executionContext, updateFiles);
var compiler = new Compiler(editor, renderer, queryParams, handleGithubCall, $('#output'), getHidingRHP, updateFiles);
executionContext.setCompiler(compiler);
function setVersionText (text) { function setVersionText (text) {
$('#version').text(text); $('#version').text(text);
......
var webworkify = require('webworkify'); var webworkify = require('webworkify');
var queryParams = require('./query-params');
var utils = require('./utils'); var utils = require('./utils');
var Renderer = require('./renderer');
var Base64 = require('js-base64').Base64; var Base64 = require('js-base64').Base64;
function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles) { function Compiler (editor, renderer, queryParams, handleGithubCall, outputField, hidingRHP, updateFiles) {
var renderer = new Renderer(editor, this, updateFiles);
var compileJSON; var compileJSON;
var compilerAcceptsMultipleFiles; var compilerAcceptsMultipleFiles;
var previousInput = ''; var previousInput = '';
var sourceAnnotations = [];
var cachedRemoteFiles = {}; var cachedRemoteFiles = {};
var worker = null; var worker = null;
...@@ -39,7 +34,6 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles ...@@ -39,7 +34,6 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles
var compile = function (missingInputs) { var compile = function (missingInputs) {
editor.clearAnnotations(); editor.clearAnnotations();
sourceAnnotations = [];
outputField.empty(); outputField.empty();
var input = editor.getValue(); var input = editor.getValue();
editor.setCacheFileContent(input); editor.setCacheFileContent(input);
...@@ -58,10 +52,10 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles ...@@ -58,10 +52,10 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles
}; };
this.compile = compile; this.compile = compile;
this.addAnnotation = function (annotation) { function setCompileJSON (_compileJSON) {
sourceAnnotations[sourceAnnotations.length] = annotation; compileJSON = _compileJSON;
editor.setAnnotations(sourceAnnotations); }
}; this.setCompileJSON = setCompileJSON; // this is exposed for testing
function onCompilerLoaded (setVersionText, version) { function onCompilerLoaded (setVersionText, version) {
setVersionText(version); setVersionText(version);
...@@ -91,14 +85,14 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles ...@@ -91,14 +85,14 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles
compilerAcceptsMultipleFiles = false; compilerAcceptsMultipleFiles = false;
compile = Module.cwrap('compileJSON', 'string', [ 'string', 'number' ]); compile = Module.cwrap('compileJSON', 'string', [ 'string', 'number' ]);
} }
compileJSON = function (source, optimize, cb) { setCompileJSON(function (source, optimize, cb) {
try { try {
var result = compile(source, optimize); var result = compile(source, optimize);
} catch (exception) { } catch (exception) {
result = JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception }); result = JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception });
} }
compilationFinished(result, missingInputs); compilationFinished(result, missingInputs);
}; });
onCompilerLoaded(setVersionText, Module.cwrap('version', 'string', [])()); onCompilerLoaded(setVersionText, Module.cwrap('version', 'string', [])());
} }
} }
...@@ -149,7 +143,7 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles ...@@ -149,7 +143,7 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles
function loadInternal (url, setVersionText) { function loadInternal (url, setVersionText) {
delete window.Module; delete window.Module;
// Set a safe fallback until the new one is loaded // Set a safe fallback until the new one is loaded
compileJSON = function (source, optimize) { compilationFinished('{}'); }; setCompileJSON(function (source, optimize) { compilationFinished('{}'); });
var newScript = document.createElement('script'); var newScript = document.createElement('script');
newScript.type = 'text/javascript'; newScript.type = 'text/javascript';
...@@ -183,9 +177,9 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles ...@@ -183,9 +177,9 @@ function Compiler (editor, handleGithubCall, outputField, hidingRHP, updateFiles
}); });
worker.onerror = function (msg) { console.log(msg.data); }; worker.onerror = function (msg) { console.log(msg.data); };
worker.addEventListener('error', function (msg) { console.log(msg.data); }); worker.addEventListener('error', function (msg) { console.log(msg.data); });
compileJSON = function (source, optimize) { setCompileJSON(function (source, optimize) {
worker.postMessage({cmd: 'compile', source: source, optimize: optimize}); worker.postMessage({cmd: 'compile', source: source, optimize: optimize});
}; });
worker.postMessage({cmd: 'loadVersion', data: url}); worker.postMessage({cmd: 'loadVersion', data: url});
} }
......
...@@ -6,6 +6,15 @@ var ace = require('brace'); ...@@ -6,6 +6,15 @@ var ace = require('brace');
require('../mode-solidity.js'); require('../mode-solidity.js');
function Editor (loadingFromGist) { function Editor (loadingFromGist) {
var SOL_CACHE_UNTITLED = utils.getCacheFilePrefix() + 'Untitled';
var SOL_CACHE_FILE = null;
var editor = ace.edit('input');
var sessions = {};
var sourceAnnotations = [];
setupStuff(getFiles());
this.newFile = function () { this.newFile = function () {
var untitledCount = ''; var untitledCount = '';
while (window.localStorage[SOL_CACHE_UNTITLED + untitledCount]) { while (window.localStorage[SOL_CACHE_UNTITLED + untitledCount]) {
...@@ -58,7 +67,7 @@ function Editor (loadingFromGist) { ...@@ -58,7 +67,7 @@ function Editor (loadingFromGist) {
return this.getFiles().indexOf(utils.fileKey(name)) !== -1; return this.getFiles().indexOf(utils.fileKey(name)) !== -1;
}; };
this.getFiles = function () { function getFiles () {
var files = []; var files = [];
for (var f in window.localStorage) { for (var f in window.localStorage) {
if (f.indexOf(utils.getCacheFilePrefix(), 0) === 0) { if (f.indexOf(utils.getCacheFilePrefix(), 0) === 0) {
...@@ -67,7 +76,8 @@ function Editor (loadingFromGist) { ...@@ -67,7 +76,8 @@ function Editor (loadingFromGist) {
} }
} }
return files; return files;
}; }
this.getFiles = getFiles;
this.packageFiles = function () { this.packageFiles = function () {
var files = {}; var files = {};
...@@ -100,9 +110,15 @@ function Editor (loadingFromGist) { ...@@ -100,9 +110,15 @@ function Editor (loadingFromGist) {
}; };
this.clearAnnotations = function () { this.clearAnnotations = function () {
sourceAnnotations = [];
editor.getSession().clearAnnotations(); editor.getSession().clearAnnotations();
}; };
this.addAnnotation = function (annotation) {
sourceAnnotations[sourceAnnotations.length] = annotation;
this.setAnnotations(sourceAnnotations);
};
this.setAnnotations = function (sourceAnnotations) { this.setAnnotations = function (sourceAnnotations) {
editor.getSession().setAnnotations(sourceAnnotations); editor.getSession().setAnnotations(sourceAnnotations);
}; };
...@@ -152,14 +168,6 @@ function Editor (loadingFromGist) { ...@@ -152,14 +168,6 @@ function Editor (loadingFromGist) {
editor.setSession(sessions[SOL_CACHE_FILE]); editor.setSession(sessions[SOL_CACHE_FILE]);
editor.resize(true); editor.resize(true);
} }
var SOL_CACHE_UNTITLED = utils.getCacheFilePrefix() + 'Untitled';
var SOL_CACHE_FILE = null;
var editor = ace.edit('input');
var sessions = {};
setupStuff(this.getFiles());
} }
module.exports = Editor; module.exports = Editor;
...@@ -13,9 +13,14 @@ if (typeof window.web3 !== 'undefined') { ...@@ -13,9 +13,14 @@ if (typeof window.web3 !== 'undefined') {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
} }
function ExecutionContext (compiler) { function ExecutionContext () {
var compiler;
var executionContext = injectedProvider ? 'injected' : 'vm'; var executionContext = injectedProvider ? 'injected' : 'vm';
this.setCompiler = function (_compiler) {
compiler = _compiler;
};
this.isVM = function () { this.isVM = function () {
return executionContext === 'vm'; return executionContext === 'vm';
}; };
......
/* global prompt */ // Allowing window to be overriden for testing
function GistHandler (_window) {
if (_window === undefined) _window = window;
var queryParams = require('./query-params'); this.handleLoad = function (params, cb) {
var loadingFromGist = false;
function handleLoad (cb) {
var params = queryParams.get();
var loadingFromGist = false;
if (typeof params['gist'] !== undefined) {
var gistId; var gistId;
if (params['gist'] === '') { if (params['gist'] === '') {
var str = prompt('Enter the URL or ID of the Gist you would like to load.'); var str = _window.prompt('Enter the URL or ID of the Gist you would like to load.');
if (str !== '') { if (str !== '') {
gistId = getGistId(str); gistId = getGistId(str);
loadingFromGist = !!gistId; loadingFromGist = !!gistId;
...@@ -20,16 +18,14 @@ function handleLoad (cb) { ...@@ -20,16 +18,14 @@ function handleLoad (cb) {
if (loadingFromGist) { if (loadingFromGist) {
cb(gistId); cb(gistId);
} }
} return loadingFromGist;
return loadingFromGist; };
}
function getGistId (str) { function getGistId (str) {
var idr = /[0-9A-Fa-f]{8,}/; var idr = /[0-9A-Fa-f]{8,}/;
var match = idr.exec(str); var match = idr.exec(str);
return match ? match[0] : null; return match ? match[0] : null;
}
} }
module.exports = { module.exports = GistHandler;
handleLoad: handleLoad
};
function getQueryParams () { // Allowing window to be overriden for testing
var qs = window.location.hash.substr(1); function QueryParams (_window) {
if (_window === undefined) _window = window;
if (window.location.search.length > 0) { this.get = function () {
// use legacy query params instead of hash var qs = _window.location.hash.substr(1);
window.location.hash = window.location.search.substr(1);
window.location.search = '';
}
var params = {}; if (_window.location.search.length > 0) {
var parts = qs.split('&'); // use legacy query params instead of hash
for (var x in parts) { _window.location.hash = _window.location.search.substr(1);
var keyValue = parts[x].split('='); _window.location.search = '';
if (keyValue[0] !== '') {
params[keyValue[0]] = keyValue[1];
} }
}
return params;
}
function updateQueryParams (params) { var params = {};
var currentParams = getQueryParams(); var parts = qs.split('&');
var keys = Object.keys(params); for (var x in parts) {
for (var x in keys) { var keyValue = parts[x].split('=');
currentParams[keys[x]] = params[keys[x]]; if (keyValue[0] !== '') {
} params[keyValue[0]] = keyValue[1];
var queryString = '#'; }
var updatedKeys = Object.keys(currentParams); }
for (var y in updatedKeys) { return params;
queryString += updatedKeys[y] + '=' + currentParams[updatedKeys[y]] + '&'; };
}
window.location.hash = queryString.slice(0, -1); this.update = function (params) {
var currentParams = this.get();
var keys = Object.keys(params);
for (var x in keys) {
currentParams[keys[x]] = params[keys[x]];
}
var queryString = '#';
var updatedKeys = Object.keys(currentParams);
for (var y in updatedKeys) {
queryString += updatedKeys[y] + '=' + currentParams[updatedKeys[y]] + '&';
}
_window.location.hash = queryString.slice(0, -1);
};
} }
module.exports = { module.exports = QueryParams;
get: getQueryParams,
update: updateQueryParams
};
...@@ -3,11 +3,9 @@ var $ = require('jquery'); ...@@ -3,11 +3,9 @@ var $ = require('jquery');
var UniversalDApp = require('../universal-dapp.js'); var UniversalDApp = require('../universal-dapp.js');
var utils = require('./utils'); var utils = require('./utils');
var ExecutionContext = require('./execution-context');
function Renderer (editor, compiler, updateFiles) { function Renderer (editor, executionContext, updateFiles) {
var detailsOpen = {}; var detailsOpen = {};
var executionContext = new ExecutionContext(compiler);
function renderError (message) { function renderError (message) {
var type = utils.errortype(message); var type = utils.errortype(message);
...@@ -20,7 +18,7 @@ function Renderer (editor, compiler, updateFiles) { ...@@ -20,7 +18,7 @@ function Renderer (editor, compiler, updateFiles) {
var errLine = parseInt(err[2], 10) - 1; var errLine = parseInt(err[2], 10) - 1;
var errCol = err[4] ? parseInt(err[4], 10) : 0; var errCol = err[4] ? parseInt(err[4], 10) : 0;
if (errFile === '' || errFile === utils.fileNameFromKey(editor.getCacheFile())) { if (errFile === '' || errFile === utils.fileNameFromKey(editor.getCacheFile())) {
compiler.addAnnotation({ editor.addAnnotation({
row: errLine, row: errLine,
column: errCol, column: errCol,
text: message, text: message,
......
var test = require('tape');
var Compiler = require('../src/app/compiler');
test('compiler.compile smoke', function (t) {
t.plan(1);
var noop = function () {};
var getCacheFile = function () { return 'fakeCacheFile'; };
var fakeEditor = {onChangeSetup: noop, clearAnnotations: noop, getValue: noop, setCacheFileContent: noop, getCacheFile: getCacheFile};
var fakeOutputField = {empty: noop};
var fakeQueryParams = {get: function () { return {}; }};
var compiler = new Compiler(fakeEditor, null, fakeQueryParams, null, fakeOutputField);
compiler.setCompileJSON(noop);
compiler.compile();
t.ok(compiler);
});
var test = require('tape');
var GistHandler = require('../src/app/gist-handler');
test('gistHandler.handleLoad with no gist param', function (t) {
t.plan(1);
var gistHandler = new GistHandler({});
var params = {};
var result = gistHandler.handleLoad(params, null);
t.equal(result, false);
});
test('gistHandler.handleLoad with blank gist param, and invalid user input', function (t) {
t.plan(3);
var fakeWindow = {prompt: function (message) {
t.ok(message);
t.ok(message.match(/gist/i));
return 'invalid';
}};
var gistHandler = new GistHandler(fakeWindow);
var params = {'gist': ''};
var result = gistHandler.handleLoad(params, null);
t.equal(result, false);
});
test('gistHandler.handleLoad with blank gist param, and valid user input', function (t) {
t.plan(4);
var fakeWindow = {prompt: function (message) {
t.ok(message);
t.ok(message.match(/gist/i));
return 'Beef1234';
}};
var cb = function (gistId) {
t.equal(gistId, 'Beef1234');
};
var gistHandler = new GistHandler(fakeWindow);
var params = {'gist': ''};
var result = gistHandler.handleLoad(params, cb);
t.equal(result, true);
});
test('gistHandler.handleLoad with gist param', function (t) {
t.plan(2);
var gistHandler = new GistHandler({});
var params = {'gist': 'abc'};
var cb = function (gistId) {
t.equal(gistId, 'abc');
};
var result = gistHandler.handleLoad(params, cb);
t.equal(result, true);
});
require('./compiler-test');
require('./gist-handler-test');
require('./query-params-test');
var test = require('tape');
var QueryParams = require('../src/app/query-params');
test('queryParams.get', function (t) {
t.plan(2);
var fakeWindow = {location: {hash: '#wat=sup&foo=bar', search: ''}};
var params = new QueryParams(fakeWindow).get();
t.equal(params.wat, 'sup');
t.equal(params.foo, 'bar');
});
test('queryParams.update', function (t) {
t.plan(1);
var fakeWindow = {location: {hash: '#wat=sup', search: ''}};
var qp = new QueryParams(fakeWindow);
qp.update({foo: 'bar'});
t.equal(fakeWindow.location.hash, '#wat=sup&foo=bar');
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment