From 4755bfcb561d37b39da4eea74893e91bf600a989 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Mon, 19 Jan 2015 23:52:39 -0500 Subject: [PATCH 01/13] Reset to working condition --- public/css/main.less | 2 +- public/js/lib/bonfire/plugin.js | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index 30ee0050f7..7c51142148 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -525,7 +525,7 @@ thead { } } -form.code { +form.code span { font-size: 16px; font-family: "Courier New", "Lucida Console", monospace; } diff --git a/public/js/lib/bonfire/plugin.js b/public/js/lib/bonfire/plugin.js index 7da28e47d3..d2cc73b68b 100644 --- a/public/js/lib/bonfire/plugin.js +++ b/public/js/lib/bonfire/plugin.js @@ -42,12 +42,7 @@ var runHidden = function(code) { var onoffline = null; var ononline = null; var importScripts = null; - var console = { - panel: $(parent.document.body).append('
'), - log: function(m){ - this.panel.prepend('
'+m+'
'); - } - }; + var console = null; var application = null; return eval(code); From cc3fa8e5f7b34271b1a3a38629882eedc656e7af Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 00:36:14 -0500 Subject: [PATCH 02/13] Configurating Console.log --- public/js/lib/bonfire/bonfire.js | 5 ++ public/js/lib/bonfire/plugin.js | 2 - views/bonfire/bonfire.jade | 99 +++++++++++++++----------------- 3 files changed, 50 insertions(+), 56 deletions(-) diff --git a/public/js/lib/bonfire/bonfire.js b/public/js/lib/bonfire/bonfire.js index 2ff6e70a4c..54d7965864 100644 --- a/public/js/lib/bonfire/bonfire.js +++ b/public/js/lib/bonfire/bonfire.js @@ -45,6 +45,11 @@ var api = { } else { print('output', data.output); } + }, + console: { + log: function(msg) { + console.log(msg); + } } }; diff --git a/public/js/lib/bonfire/plugin.js b/public/js/lib/bonfire/plugin.js index d2cc73b68b..85c42a3c77 100644 --- a/public/js/lib/bonfire/plugin.js +++ b/public/js/lib/bonfire/plugin.js @@ -42,8 +42,6 @@ var runHidden = function(code) { var onoffline = null; var ononline = null; var importScripts = null; - var console = null; - var application = null; return eval(code); } diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 0bfea95f67..47605cc9ff 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -47,29 +47,11 @@ block content .panel-heading Test Suite .panel.panel-body ul#testSuite.list-group - br - #runTests.btn.btn-primary.btn-big.btn-block Run my test suite + br + + #runTests.btn.btn-primary.btn-big.btn-block Run my test suite textarea#testOutput - //#hintButton.btn.btn-info.btn-big.btn-block Show me hints script. - //Button for moving test window to side - $('#sideBySide').on('click', function () { - var main = $('#mainEditorPanel'); - if (main.hasClass('col-md-12')) { - replaceColClz(main, 'md', 12, 6); - replaceColClz(main, 'sm', 12, 6); - $(this).text("Original Layout") - } else { - replaceColClz(main, 'md', 6, 12); - replaceColClz(main, 'sm', 6, 12); - $(this).text("Tests side by side") - } - }); - //Replace a bootstrap column number by browser size - var replaceColClz = function (elt, size, oldVal, newVal) { - elt.removeClass('col-' + size + '-' + oldVal); - elt.addClass('col-' + size + '-' + newVal); - }; var widgets = []; var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { lineNumbers: true, @@ -133,10 +115,10 @@ block content submit(js); }); var assert = chai.assert; + var testResults = []; $('#runTests').on('click', function () { clearTestOutput(); var testCaseList = [], - testResults = [], jsCode = myCodeMirror.getValue(); getTestSuite().each(function () { testCaseList.push([$(this).data("input"), $(this).data("output"), $(this)]); @@ -145,44 +127,53 @@ block content var testCode = jsCode + "\n\n" + input[0] + ";"; //TODO use plugin for this with the rest as a callback? var output = eval(testCode); - try { - testEquality(output, input[1]); - appendTestOutput("\n" + createTestString(input[0], input[1]) + "\nTest passed!\n"); - setTestBackground(input[2], "passed"); - testResults.push(1); - } catch (err) { - setTestBackground(input[2], "failed"); - appendTestOutput(createTestString(input[0], input[1])); - appendTestOutput("Test failed: \nOutput was: " + output + "\nType of output was: " + (typeof output)); - testResults.push(0); - } - if (testResults.length === testCaseList.length) { - var sum = testResults.reduce(function (a, b) { - return a + b - }); - prependTestOutput("======Testing========\n" + Math.round(100 * sum / testResults.length) + "% tests passed\n"); - } + testEquality(output, input); }); + // some timeout here? + if (testResults.length === testCaseList.length) { + var sum = testResults.reduce(function (a, b) { + return a + b + }); + prependTestOutput("======Testing========\n" + Math.round(100 * sum / testResults.length) + "% tests passed\n"); + } }); - //After looking at chai assert, this is the way to go if you don't know the type var testEquality = function (output, input) { - switch (typeof output) { - case 'object': - assert.deepEqual(output, input); - break; - case 'string': - assert(output.localeCompare(input)); - break - default: - assert.equal(output, input); + try { + switch (typeof output) { + case 'object': + assert.deepEqual(output, input[1]); + break; + case 'string': + assert(output.localeCompare(input[1])); + break + default: + assert.equal(output, input[1]); + } + appendTestOutput("\n" + createTestString(input[0], input[1]) + "\nTest passed!\n"); + input[2].css("background-color", "rgba(0,255,0,.2)"); + testResults.push(1); + } catch (err) { + input[2].css("background-color", "rgba(255,0,0,.2)"); + appendTestOutput(createTestString(input[0], input[1])); + appendTestOutput("Test failed: \nOutput was: " + output + "\nType of output was: " + (typeof output)); + testResults.push(0); } }; - var setTestBackground = function (elt, result) { - if (result.localeCompare('failed')) { - elt.css("background-color", "rgba(255,0,0,.2)"); + $('#sideBySide').on('click', function () { + var main = $('#mainEditorPanel'); + if (main.hasClass('col-md-12')) { + replaceColClz(main, 'md', 12, 6); + replaceColClz(main, 'sm', 12, 6); + $(this).text("Original Layout") } else { - elt.css("background-color", "rgba(0,255,0,.2)"); + replaceColClz(main, 'md', 6, 12); + replaceColClz(main, 'sm', 6, 12); + $(this).text("Tests side by side") } + }); + var replaceColClz = function (elt, size, oldVal, newVal) { + elt.removeClass('col-' + size + '-' + oldVal); + elt.addClass('col-' + size + '-' + newVal); }; var getTestSuite = function () { return $('#testSuite').find('li'); @@ -252,7 +243,7 @@ block content var m = re.exec(code); while (m != null) { var functionName = m[1]; - if (functionName !== undefined && currentState.length === 0) { + if (functionName !== undefined) { var option = document.createElement('option'); $(option) .html(functionName) From bb87cc4bb3ecce9e30aca214811718a63f34282b Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 02:19:39 -0500 Subject: [PATCH 03/13] Pre View configurating --- public/js/lib/bonfire/bonfire.js | 7 +------ public/js/lib/bonfire/{plugin.js => plugin_v0.1.1.js} | 0 views/bonfire/bonfire.jade | 6 ++++-- 3 files changed, 5 insertions(+), 8 deletions(-) rename public/js/lib/bonfire/{plugin.js => plugin_v0.1.1.js} (100%) diff --git a/public/js/lib/bonfire/bonfire.js b/public/js/lib/bonfire/bonfire.js index 54d7965864..c5eefd75ff 100644 --- a/public/js/lib/bonfire/bonfire.js +++ b/public/js/lib/bonfire/bonfire.js @@ -45,11 +45,6 @@ var api = { } else { print('output', data.output); } - }, - console: { - log: function(msg) { - console.log(msg); - } } }; @@ -69,7 +64,7 @@ var requests; // (re)initializes the plugin var reset = function() { requests = 0; - plugin = new jailed.Plugin(path+'plugin.js', api); + plugin = new jailed.Plugin(path+'plugin_v0.1.1.js', api); plugin.whenDisconnected( function() { // give some time to handle the last responce setTimeout( function() { diff --git a/public/js/lib/bonfire/plugin.js b/public/js/lib/bonfire/plugin_v0.1.1.js similarity index 100% rename from public/js/lib/bonfire/plugin.js rename to public/js/lib/bonfire/plugin_v0.1.1.js diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 47605cc9ff..1a2d02f54d 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -76,6 +76,7 @@ block content '}\n\n' + 'test();'); myCodeMirror.setSize("100%", 500); + var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { lineNumbers: false, mode: "text", @@ -196,8 +197,9 @@ block content var output; $('#testInputs').find('input').each(function () { if ($(this).val() != null && $(this).val().length !== 0) { + inputs.push($(this).val()); } else { - var keepGoing = prompt("You have submitted a test with empty input. Enter yes to continue."); + //var keepGoing = prompt("You have submitted a test with empty input. Enter yes to continue."); if (/yes/.test(keepGoing.toLowerCase())) { inputs.push($(this).val()); } else { @@ -221,7 +223,7 @@ block content .addClass("glyphicon glyphicon-remove-sign") .css("float", "right") .click(function () { - var input = prompt("This will remove the test permanently.\n If you want to do this, type delete"); + //var input = prompt("This will remove the test permanently.\n If you want to do this, type delete"); if (/delete/.test(input.toLowerCase())) { $(this).parent().remove(); } From bfb711166ce8b50d5494dde736c04b4552adffbe Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 03:10:24 -0500 Subject: [PATCH 04/13] further improvement of bonfire view --- public/css/main.less | 14 +++++++ public/js/lib/codemirror/lib/codemirror.css | 2 +- views/bonfire/bonfire.jade | 44 ++++++++------------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index 7c51142148..b8b623428c 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -525,9 +525,23 @@ thead { } } +form.code div { + border-radius: 5px; +} form.code span { font-size: 16px; font-family: "Courier New", "Lucida Console", monospace; + padding-bottom: 0px; + margin-bottom: 0px; + +} + +#mainEditorPanel .panel-body { + padding-bottom: 0px; +} + +.panel-bonfire { + min-height: 590px; } //uncomment this to see the dimensions of all elements outlined in red diff --git a/public/js/lib/codemirror/lib/codemirror.css b/public/js/lib/codemirror/lib/codemirror.css index c56510e99a..d4832bd020 100644 --- a/public/js/lib/codemirror/lib/codemirror.css +++ b/public/js/lib/codemirror/lib/codemirror.css @@ -151,7 +151,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} /* 30px is the magic margin used to hide the element's real scrollbars */ /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; + padding-bottom: 0px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 1a2d02f54d..84f9d3c425 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -14,43 +14,31 @@ block content script(src='js/lib/jailed/jailed.js') script(src='/js/lib/bonfire/bonfire.js') .row - #mainEditorPanel.col-sm-12.col-md-12.col-xs-12 - .panel.panel-primary + #mainEditorPanel.col-sm-12.col-md-7.col-xs-12 + .panel.panel-primary.panel-bonfire .panel-heading.text-center Bonfire Playground .panel.panel-body form.code .form-group.codeMirrorView textarea#codeEditor(autofocus=true) + + + #testCreatePanel.col-sm-12.col-md-5.col-xs-12 + .panel.panel-primary.panel-bonfire + .panel-heading.text-center Output + .panel.panel-body + ul#testSuite.list-group + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) + form#testCreateForm.form-horizontal + br form.code .form-group.codeMirrorView textarea#codeOutput - #submitButton.btn.btn-primary.btn-big.btn-block Run my code - .hidden-xs - br - #sideBySide.btn.btn-primary.btn-block.btn-big Tests side by side - #testCreatePanel.col-sm-6.col-md-6.col-xs-12 - .panel.panel-primary - .panel-heading Test Code - .panel.panel-body - form#testCreateForm.form-horizontal - .form-group - label(for='function-select').col-sm-6 Select function - select#testFunctionName.col-sm-6.text-info - option - #testInputs.form-group - #testOutputs.form-group - br - #addTest.btn.text-info Create test - #hideTestCreate.btn.text-info Hide test creation dialogue - #testSuitePanel.col-sm-6.col-md-6.col-xs-12 - .panel.panel-primary - .panel-heading Test Suite - .panel.panel-body - ul#testSuite.list-group + + #testInputs.form-group + #testOutputs.form-group br - #runTests.btn.btn-primary.btn-big.btn-block Run my test suite - textarea#testOutput script. var widgets = []; var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { @@ -221,7 +209,7 @@ block content var closeSpan = document.createElement('span'); $(closeSpan) .addClass("glyphicon glyphicon-remove-sign") - .css("float", "right") + //.css("float", "right") .click(function () { //var input = prompt("This will remove the test permanently.\n If you want to do this, type delete"); if (/delete/.test(input.toLowerCase())) { From 7e46af3d4225f54069558054c2f4b705f719d268 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 15:21:11 -0500 Subject: [PATCH 05/13] Major refactor of Bonfire.jade to pull out all JS and put into framework.js, resizing font to 14px and setting to Ubuntu Mono, permanently change layout of bonfire.jade to 2 column responsive --- public/css/main.less | 18 +- public/js/lib/bonfire/framework.js | 265 +++++++++++++++++++++ public/js/lib/codemirror/theme/monokai.css | 2 +- views/bonfire/bonfire.jade | 260 +------------------- 4 files changed, 275 insertions(+), 270 deletions(-) create mode 100644 public/js/lib/bonfire/framework.js diff --git a/public/css/main.less b/public/css/main.less index b8b623428c..226d752449 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -525,15 +525,11 @@ thead { } } -form.code div { - border-radius: 5px; -} form.code span { - font-size: 16px; - font-family: "Courier New", "Lucida Console", monospace; + font-size: 14px; + font-family: "Ubuntu Mono"; padding-bottom: 0px; margin-bottom: 0px; - } #mainEditorPanel .panel-body { @@ -545,8 +541,8 @@ form.code span { } //uncomment this to see the dimensions of all elements outlined in red -//* { -// border-color: red; -// border-width: 1px; -// border-style: solid; -//} +* { + border-color: red; + border-width: 1px; + border-style: solid; +} diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js new file mode 100644 index 0000000000..86bbefffc4 --- /dev/null +++ b/public/js/lib/bonfire/framework.js @@ -0,0 +1,265 @@ +var widgets = []; +var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { + lineNumbers: true, + mode: "javascript", + theme: 'monokai', + runnable: true, + lint: true, + matchBrackets: true, + autoCloseBrackets: true, + cursorHeight: 0.85, + lineWrapping: true, + gutters: ["CodeMirror-lint-markers"], + onKeyEvent: doLinting, + extraKeys : { + "Ctrl-Enter" : function() { + submit(myCodeMirror.getValue()); + return false; + } + } +}); +var editor = myCodeMirror; +myCodeMirror.setValue('/*Welcome to Bonfire, Free Code Camp\'s future CoderByte replacement.\n' + +'Please feel free to use Bonfire as an in-browser playground and linting tool.*/\n\n\n' + +'function test() {\n' + +' return [1,2,3].map(function(elem) {\n' + +' return elem * elem;\n' + +' });\n' + +'}\n\n' + +'test();'); +myCodeMirror.setSize("100%", 500); + +var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { + lineNumbers: false, + mode: "text", + theme: 'monokai', + readOnly: 'nocursor', + lineWrapping: true +}); +codeOutput.setSize("100%", 100); +var info = editor.getScrollInfo(); +var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; +if (info.top + info.clientHeight < after) + editor.scrollTo(null, after - info.clientHeight + 3); +var doLinting = function () { + editor.operation(function () { + for (var i = 0; i < widgets.length; ++i) + editor.removeLineWidget(widgets[i]); + widgets.length = 0; + JSHINT(editor.getValue()); + for (var i = 0; i < JSHINT.errors.length; ++i) { + var err = JSHINT.errors[i]; + if (!err) continue; + var msg = document.createElement("div"); + var icon = msg.appendChild(document.createElement("span")); + icon.innerHTML = "!!"; + icon.className = "lint-error-icon"; + msg.appendChild(document.createTextNode(err.reason)); + msg.className = "lint-error"; + widgets.push(editor.addLineWidget(err.line - 1, msg, { + coverGutter: false, + noHScroll: true + })); + } + }); +}; +$('#submitButton').on('click', function () { + $('#codeOutput').empty(); + var js = myCodeMirror.getValue(); + submit(js); +}); + +var assert = chai.assert; +var testResults = []; +$('#runTests').on('click', function () { + clearTestOutput(); + var testCaseList = [], + jsCode = myCodeMirror.getValue(); + getTestSuite().each(function () { + testCaseList.push([$(this).data("input"), $(this).data("output"), $(this)]); + }); + testCaseList.forEach(function (input) { + var testCode = jsCode + "\n\n" + input[0] + ";"; + //TODO use plugin for this with the rest as a callback? + var output = eval(testCode); + testEquality(output, input); + }); + // some timeout here? + if (testResults.length === testCaseList.length) { + var sum = testResults.reduce(function (a, b) { + return a + b + }); + prependTestOutput("======Testing========\n" + Math.round(100 * sum / testResults.length) + "% tests passed\n"); + } +}); +var testEquality = function (output, input) { + try { + switch (typeof output) { + case 'object': + assert.deepEqual(output, input[1]); + break; + case 'string': + assert(output.localeCompare(input[1])); + break + default: + assert.equal(output, input[1]); + } + appendTestOutput("\n" + createTestString(input[0], input[1]) + "\nTest passed!\n"); + input[2].css("background-color", "rgba(0,255,0,.2)"); + testResults.push(1); + } catch (err) { + input[2].css("background-color", "rgba(255,0,0,.2)"); + appendTestOutput(createTestString(input[0], input[1])); + appendTestOutput("Test failed: \nOutput was: " + output + "\nType of output was: " + (typeof output)); + testResults.push(0); + } +}; +//$('#sideBySide').on('click', function () { +// var main = $('#mainEditorPanel'); +// if (main.hasClass('col-md-12')) { +// replaceColClz(main, 'md', 12, 6); +// replaceColClz(main, 'sm', 12, 6); +// $(this).text("Original Layout") +// } else { +// replaceColClz(main, 'md', 6, 12); +// replaceColClz(main, 'sm', 6, 12); +// $(this).text("Tests side by side") +// } +//}); +//var replaceColClz = function (elt, size, oldVal, newVal) { +// elt.removeClass('col-' + size + '-' + oldVal); +// elt.addClass('col-' + size + '-' + newVal); +//}; +//var getTestSuite = function () { +// return $('#testSuite').find('li'); +//}; +//var clearTestOutput = function () { +// testOutput.setValue(""); +//}; +var appendTestOutput = function (msg) { + writeToTest(msg, CodeMirror.Pos(editor.lastLine())); +}; +var prependTestOutput = function (msg) { + writeToTest(msg, CodeMirror.Pos(editor.firstLine())); +}; +var writeToTest = function (msg, location) { + testOutput.replaceRange("\n" + msg, location); +}; +//$('#addTest').on('click', function () { +// var functionName = $('#testFunctionName option:selected').text(); +// var inputs = []; +// var output; +// $('#testInputs').find('input').each(function () { +// if ($(this).val() != null && $(this).val().length !== 0) { +// inputs.push($(this).val()); +// } else { +// //var keepGoing = prompt("You have submitted a test with empty input. Enter yes to continue."); +// if (/yes/.test(keepGoing.toLowerCase())) { +// inputs.push($(this).val()); +// } else { +// return; +// } +// } +// }); +// output = $('#testOutputs').find('input').val(); +// var functionCall = functionName + "(" + inputs.join(",") + ")"; +// var test = document.createElement("li"); +// $(test) +// .addClass('list-group-item') +// .addClass('well') +// .addClass('well-sm') +// .attr({"data-input": functionCall, "data-output": output}) +// .html(createTestString(functionCall, output)) +// .appendTo($('#testSuite')); +// var closeLink = document.createElement('i'); +// var closeSpan = document.createElement('span'); +// $(closeSpan) +// .addClass("glyphicon glyphicon-remove-sign") +// //.css("float", "right") +// .click(function () { +// //var input = prompt("This will remove the test permanently.\n If you want to do this, type delete"); +// if (/delete/.test(input.toLowerCase())) { +// $(this).parent().remove(); +// } +// }).appendTo($(test)); +// //blank out the form +// $("#testCreateForm").find("input[type=text]").val(""); +//}); +//var createTestString = function (inputs, output) { +// return "Input: " + inputs + "\nExpect:" + output; +//}; +//var testOutput = CodeMirror.fromTextArea(document.getElementById("testOutput"), { +// lineNumbers: false, +// mode: "javascript", +// theme: 'monokai', +// readOnly: 'nocursor' +//}); +//testOutput.setSize("100%", 200); +//var createOptions = function (re, code) { +// var m = re.exec(code); +// while (m != null) { +// var functionName = m[1]; +// if (functionName !== undefined) { +// var option = document.createElement('option'); +// $(option) +// .html(functionName) +// .attr({"data-args": m[2]}) +// .appendTo($('#testFunctionName')); +// } +// m = re.exec(code); +// } +//}; +$('#testFunctionName').on('change', function () { + $('#testInputs').children().remove(); + $('#testOutputs').children().remove(); + var args = $('#testFunctionName option:selected').data("args"); + var argArray = args.split(","); + argArray.forEach(function (arg) { + if (arg.length > 0) { + createInputField('#testInputs', arg); + } + }); + createInputField('#testOutputs', 'Expected output'); +}); +var createInputField = function (className, arg) { + var inputDiv = document.createElement('div'); + $(inputDiv) + .addClass("control-group") + .appendTo($(className)); + var inputLabel = document.createElement('label'); + $(inputLabel) + .attr("for", "inputs") + .html(arg) + .addClass("col-xs-4 control-label") + .appendTo($(inputDiv)); + var textDiv = document.createElement('div'); + $(textDiv) + .addClass("col-xs-8 controls") + .appendTo($(inputDiv)); + var inputArea = document.createElement('input'); + $(inputArea) + .attr("type", "text") + .addClass("form-control") + .appendTo($(inputDiv)); + $(document.createElement("br")).appendTo($(textDiv)); +}; +$('#testFunctionName').on('focus', function () { + $('#testFunctionName').children().remove(); + var blankOpt = document.createElement("option"); + $(blankOpt).addClass("selected").appendTo($('#testFunctionName')); + var re = /function\s+(\w+)\s*\(([\w\s,]*)\)/g; + var code = myCodeMirror.getValue(); + createOptions(re, code); + re = /var (\w+)\s*=\s*function\s*\(([\s\w,]*)\)/g; + createOptions(re, code); +}); +$('#hideTestCreate').on('click', function () { + var testForm = $("#testCreateForm"); + if (testForm.is(":visible")) { + testForm.hide(); + $(this).text("Create more tests"); + } else { + testForm.show(); + $(this).text("Hide test creation dialogue") + } +}); \ No newline at end of file diff --git a/public/js/lib/codemirror/theme/monokai.css b/public/js/lib/codemirror/theme/monokai.css index 548d2dfff6..74c3d2d903 100644 --- a/public/js/lib/codemirror/theme/monokai.css +++ b/public/js/lib/codemirror/theme/monokai.css @@ -1,6 +1,6 @@ /* Based on Sublime Text's Monokai theme */ -.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;} +.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2; border-radius: 5px;} .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} .cm-s-monokai .CodeMirror-guttermarker { color: white; } diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 84f9d3c425..4463d7656d 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -10,6 +10,7 @@ block content link(rel='stylesheet', href='/js/lib/codemirror/lib/codemirror.css') link(rel='stylesheet', href='/js/lib/codemirror/addon/lint/lint.css') link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css') + link(rel="stylesheet", href="http://fonts.googleapis.com/css?family=Ubuntu+Mono") script(src='/js/lib/codemirror/mode/javascript/javascript.js') script(src='js/lib/jailed/jailed.js') script(src='/js/lib/bonfire/bonfire.js') @@ -39,261 +40,4 @@ block content #testOutputs.form-group br - script. - var widgets = []; - var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { - lineNumbers: true, - mode: "javascript", - theme: 'monokai', - runnable: true, - lint: true, - matchBrackets: true, - autoCloseBrackets: true, - cursorHeight: 0.85, - lineWrapping: true, - gutters: ["CodeMirror-lint-markers"], - onKeyEvent: doLinting - }); - var editor = myCodeMirror; - myCodeMirror.setValue('/*Welcome to Bonfire, Free Code Camp\'s future CoderByte replacement.\n' + - 'Please feel free to use Bonfire as an in-browser playground and linting tool.*/\n\n\n' + - 'function test() {\n' + - ' return [1,2,3].map(function(elem) {\n' + - ' return elem * elem;\n' + - ' });\n' + - '}\n\n' + - 'test();'); - myCodeMirror.setSize("100%", 500); - - var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { - lineNumbers: false, - mode: "text", - theme: 'monokai', - readOnly: 'nocursor' - }); - codeOutput.setSize("100%", 100); - var info = editor.getScrollInfo(); - var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; - if (info.top + info.clientHeight < after) - editor.scrollTo(null, after - info.clientHeight + 3); - var doLinting = function () { - editor.operation(function () { - for (var i = 0; i < widgets.length; ++i) - editor.removeLineWidget(widgets[i]); - widgets.length = 0; - JSHINT(editor.getValue()); - for (var i = 0; i < JSHINT.errors.length; ++i) { - var err = JSHINT.errors[i]; - if (!err) continue; - var msg = document.createElement("div"); - var icon = msg.appendChild(document.createElement("span")); - icon.innerHTML = "!!"; - icon.className = "lint-error-icon"; - msg.appendChild(document.createTextNode(err.reason)); - msg.className = "lint-error"; - widgets.push(editor.addLineWidget(err.line - 1, msg, { - coverGutter: false, - noHScroll: true - })); - } - }); - }; - $('#submitButton').on('click', function () { - $('#codeOutput').empty(); - var js = myCodeMirror.getValue(); - submit(js); - }); - var assert = chai.assert; - var testResults = []; - $('#runTests').on('click', function () { - clearTestOutput(); - var testCaseList = [], - jsCode = myCodeMirror.getValue(); - getTestSuite().each(function () { - testCaseList.push([$(this).data("input"), $(this).data("output"), $(this)]); - }); - testCaseList.forEach(function (input) { - var testCode = jsCode + "\n\n" + input[0] + ";"; - //TODO use plugin for this with the rest as a callback? - var output = eval(testCode); - testEquality(output, input); - }); - // some timeout here? - if (testResults.length === testCaseList.length) { - var sum = testResults.reduce(function (a, b) { - return a + b - }); - prependTestOutput("======Testing========\n" + Math.round(100 * sum / testResults.length) + "% tests passed\n"); - } - }); - var testEquality = function (output, input) { - try { - switch (typeof output) { - case 'object': - assert.deepEqual(output, input[1]); - break; - case 'string': - assert(output.localeCompare(input[1])); - break - default: - assert.equal(output, input[1]); - } - appendTestOutput("\n" + createTestString(input[0], input[1]) + "\nTest passed!\n"); - input[2].css("background-color", "rgba(0,255,0,.2)"); - testResults.push(1); - } catch (err) { - input[2].css("background-color", "rgba(255,0,0,.2)"); - appendTestOutput(createTestString(input[0], input[1])); - appendTestOutput("Test failed: \nOutput was: " + output + "\nType of output was: " + (typeof output)); - testResults.push(0); - } - }; - $('#sideBySide').on('click', function () { - var main = $('#mainEditorPanel'); - if (main.hasClass('col-md-12')) { - replaceColClz(main, 'md', 12, 6); - replaceColClz(main, 'sm', 12, 6); - $(this).text("Original Layout") - } else { - replaceColClz(main, 'md', 6, 12); - replaceColClz(main, 'sm', 6, 12); - $(this).text("Tests side by side") - } - }); - var replaceColClz = function (elt, size, oldVal, newVal) { - elt.removeClass('col-' + size + '-' + oldVal); - elt.addClass('col-' + size + '-' + newVal); - }; - var getTestSuite = function () { - return $('#testSuite').find('li'); - }; - var clearTestOutput = function () { - testOutput.setValue(""); - }; - var appendTestOutput = function (msg) { - writeToTest(msg, CodeMirror.Pos(editor.lastLine())); - }; - var prependTestOutput = function (msg) { - writeToTest(msg, CodeMirror.Pos(editor.firstLine())); - }; - var writeToTest = function (msg, location) { - testOutput.replaceRange("\n" + msg, location); - }; - $('#addTest').on('click', function () { - var functionName = $('#testFunctionName option:selected').text(); - var inputs = []; - var output; - $('#testInputs').find('input').each(function () { - if ($(this).val() != null && $(this).val().length !== 0) { - inputs.push($(this).val()); - } else { - //var keepGoing = prompt("You have submitted a test with empty input. Enter yes to continue."); - if (/yes/.test(keepGoing.toLowerCase())) { - inputs.push($(this).val()); - } else { - return; - } - } - }); - output = $('#testOutputs').find('input').val(); - var functionCall = functionName + "(" + inputs.join(",") + ")"; - var test = document.createElement("li"); - $(test) - .addClass('list-group-item') - .addClass('well') - .addClass('well-sm') - .attr({"data-input": functionCall, "data-output": output}) - .html(createTestString(functionCall, output)) - .appendTo($('#testSuite')); - var closeLink = document.createElement('i'); - var closeSpan = document.createElement('span'); - $(closeSpan) - .addClass("glyphicon glyphicon-remove-sign") - //.css("float", "right") - .click(function () { - //var input = prompt("This will remove the test permanently.\n If you want to do this, type delete"); - if (/delete/.test(input.toLowerCase())) { - $(this).parent().remove(); - } - }).appendTo($(test)); - //blank out the form - $("#testCreateForm").find("input[type=text]").val(""); - }); - var createTestString = function (inputs, output) { - return "Input: " + inputs + "\nExpect:" + output; - }; - var testOutput = CodeMirror.fromTextArea(document.getElementById("testOutput"), { - lineNumbers: false, - mode: "javascript", - theme: 'monokai', - readOnly: 'nocursor' - }); - testOutput.setSize("100%", 200); - var createOptions = function (re, code) { - var m = re.exec(code); - while (m != null) { - var functionName = m[1]; - if (functionName !== undefined) { - var option = document.createElement('option'); - $(option) - .html(functionName) - .attr({"data-args": m[2]}) - .appendTo($('#testFunctionName')); - } - m = re.exec(code); - } - }; - $('#testFunctionName').on('change', function () { - $('#testInputs').children().remove(); - $('#testOutputs').children().remove(); - var args = $('#testFunctionName option:selected').data("args"); - var argArray = args.split(","); - argArray.forEach(function (arg) { - if (arg.length > 0) { - createInputField('#testInputs', arg); - } - }); - createInputField('#testOutputs', 'Expected output'); - }); - var createInputField = function (className, arg) { - var inputDiv = document.createElement('div'); - $(inputDiv) - .addClass("control-group") - .appendTo($(className)); - var inputLabel = document.createElement('label'); - $(inputLabel) - .attr("for", "inputs") - .html(arg) - .addClass("col-xs-4 control-label") - .appendTo($(inputDiv)); - var textDiv = document.createElement('div'); - $(textDiv) - .addClass("col-xs-8 controls") - .appendTo($(inputDiv)); - var inputArea = document.createElement('input'); - $(inputArea) - .attr("type", "text") - .addClass("form-control") - .appendTo($(inputDiv)); - $(document.createElement("br")).appendTo($(textDiv)); - }; - $('#testFunctionName').on('focus', function () { - $('#testFunctionName').children().remove(); - var blankOpt = document.createElement("option"); - $(blankOpt).addClass("selected").appendTo($('#testFunctionName')); - var re = /function\s+(\w+)\s*\(([\w\s,]*)\)/g; - var code = myCodeMirror.getValue(); - createOptions(re, code); - re = /var (\w+)\s*=\s*function\s*\(([\s\w,]*)\)/g; - createOptions(re, code); - }); - $('#hideTestCreate').on('click', function () { - var testForm = $("#testCreateForm"); - if (testForm.is(":visible")) { - testForm.hide(); - $(this).text("Create more tests"); - } else { - testForm.show(); - $(this).text("Hide test creation dialogue") - } - }); \ No newline at end of file + script(src='/js/lib/bonfire/framework.js') From 15eb0c0ef0bf16eba01e0bb6e55db1d7ce4c44c7 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 15:22:04 -0500 Subject: [PATCH 06/13] Turn off css boxing for view debugging --- public/css/main.less | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index 226d752449..d723d2c4e1 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -541,8 +541,8 @@ form.code span { } //uncomment this to see the dimensions of all elements outlined in red -* { - border-color: red; - border-width: 1px; - border-style: solid; -} +//* { +// border-color: red; +// border-width: 1px; +// border-style: solid; +//} From a4dc2f02069a261705f87c66058b41464167dc79 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 16:44:09 -0500 Subject: [PATCH 07/13] marginal steps towards removing scrollbars and scrolling from bonfire viewports --- public/js/lib/bonfire/framework.js | 8 ++++++-- public/js/lib/codemirror/lib/codemirror.css | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js index 86bbefffc4..71a682dce4 100644 --- a/public/js/lib/bonfire/framework.js +++ b/public/js/lib/bonfire/framework.js @@ -27,7 +27,7 @@ myCodeMirror.setValue('/*Welcome to Bonfire, Free Code Camp\'s future CoderByte ' });\n' + '}\n\n' + 'test();'); -myCodeMirror.setSize("100%", 500); +myCodeMirror.setSize("100%", "100%"); var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { lineNumbers: false, @@ -36,7 +36,11 @@ var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), readOnly: 'nocursor', lineWrapping: true }); -codeOutput.setSize("100%", 100); +codeOutput.setValue('/**\n' + + ' * Your output will go here. Console statements\n' + + ' * will appear in your developer console!\n' + + ' */'); +codeOutput.setSize("100%", "100%"); var info = editor.getScrollInfo(); var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; if (info.top + info.clientHeight < after) diff --git a/public/js/lib/codemirror/lib/codemirror.css b/public/js/lib/codemirror/lib/codemirror.css index d4832bd020..cb705fa0a2 100644 --- a/public/js/lib/codemirror/lib/codemirror.css +++ b/public/js/lib/codemirror/lib/codemirror.css @@ -152,6 +152,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; padding-bottom: 0px; + border-radius: 5px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; From 202805dc523ce98d57d17ed06c26ef5e81f42a74 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Tue, 20 Jan 2015 20:35:25 -0500 Subject: [PATCH 08/13] preparing for integration of testing --- public/css/main.less | 5 +++-- public/js/lib/bonfire/bonfire.js | 9 +++++++-- public/js/lib/bonfire/framework.js | 5 +++-- public/js/lib/bonfire/plugin_v0.1.1.js | 7 +++++-- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/public/css/main.less b/public/css/main.less index d723d2c4e1..a4492ed832 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -536,8 +536,9 @@ form.code span { padding-bottom: 0px; } -.panel-bonfire { - min-height: 590px; +div.CodeMirror-scroll { + padding-bottom: 100px; + } //uncomment this to see the dimensions of all elements outlined in red diff --git a/public/js/lib/bonfire/bonfire.js b/public/js/lib/bonfire/bonfire.js index c5eefd75ff..5b9868bcf5 100644 --- a/public/js/lib/bonfire/bonfire.js +++ b/public/js/lib/bonfire/bonfire.js @@ -16,7 +16,12 @@ var submit = function(code) { // puts the message on the terminal var print = function(cls, msg) { - codeOutput.setValue(msg); + if (cls) { + codeOutput.setValue(msg); + } else { + codeOutput.setValue(msg.output); + console.log(msg.type); + } }; @@ -43,7 +48,7 @@ var api = { if (data.error) { print('message', data.error); } else { - print('output', data.output); + print(null, data); } } }; diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js index 71a682dce4..51d8d5d6e4 100644 --- a/public/js/lib/bonfire/framework.js +++ b/public/js/lib/bonfire/framework.js @@ -8,6 +8,7 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor") matchBrackets: true, autoCloseBrackets: true, cursorHeight: 0.85, + scrollbarStyle: 'null', lineWrapping: true, gutters: ["CodeMirror-lint-markers"], onKeyEvent: doLinting, @@ -25,9 +26,9 @@ myCodeMirror.setValue('/*Welcome to Bonfire, Free Code Camp\'s future CoderByte ' return [1,2,3].map(function(elem) {\n' + ' return elem * elem;\n' + ' });\n' + -'}\n\n' + +'}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + 'test();'); -myCodeMirror.setSize("100%", "100%"); +//myCodeMirror.setSize("100%", "100%"); var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { lineNumbers: false, diff --git a/public/js/lib/bonfire/plugin_v0.1.1.js b/public/js/lib/bonfire/plugin_v0.1.1.js index 85c42a3c77..3c9942617b 100644 --- a/public/js/lib/bonfire/plugin_v0.1.1.js +++ b/public/js/lib/bonfire/plugin_v0.1.1.js @@ -4,11 +4,14 @@ var run = function(code) { var result = { input: code, output: null, - error: null + error: null, + type: null }; try { - result.output = stringify(runHidden(code)); + var codeExec = runHidden(code); + result.type = typeof codeExec; + result.output = stringify(codeExec); } catch(e) { result.error = e.message; } From 03d635c65f0905979b3ae4c4b2156e3402db64fe Mon Sep 17 00:00:00 2001 From: jmcshane Date: Tue, 20 Jan 2015 23:31:36 -0500 Subject: [PATCH 09/13] Test evaluation complete, need to hook pre-submit --- controllers/bonfire.js | 2 +- public/js/lib/bonfire/bonfire.js | 16 +- public/js/lib/bonfire/framework.js | 263 ++++++++++++++++++----------- views/bonfire/bonfire.jade | 8 +- 4 files changed, 175 insertions(+), 114 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index c37b793b6d..86421feeaa 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -1,6 +1,6 @@ var _ = require('lodash'), debug = require('debug')('freecc:cntr:bonfires'), - bonfire = require('./../models/bonfire'); + bonfire = require('./../models/Bonfire'); /** * Bonfire controller diff --git a/public/js/lib/bonfire/bonfire.js b/public/js/lib/bonfire/bonfire.js index 5b9868bcf5..720adb22c9 100644 --- a/public/js/lib/bonfire/bonfire.js +++ b/public/js/lib/bonfire/bonfire.js @@ -1,6 +1,7 @@ - +var printCallback; // sends the input to the plugin for evaluation -var submit = function(code) { +var submit = function(code,callback) { + printCallback = callback; // postpone the evaluation until the plugin is initialized plugin.whenConnected( function() { @@ -16,12 +17,7 @@ var submit = function(code) { // puts the message on the terminal var print = function(cls, msg) { - if (cls) { - codeOutput.setValue(msg); - } else { - codeOutput.setValue(msg.output); - console.log(msg.type); - } + printCallback(cls,msg); }; @@ -44,9 +40,9 @@ var disconnect = function() { var api = { output: function(data) { endLoading(); - print('input', data.input); + //print('input', data.input); if (data.error) { - print('message', data.error); + print('Error', data); } else { print(null, data); } diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js index 51d8d5d6e4..8a7abbfcdf 100644 --- a/public/js/lib/bonfire/framework.js +++ b/public/js/lib/bonfire/framework.js @@ -68,57 +68,124 @@ var doLinting = function () { } }); }; +var assert = chai.assert; $('#submitButton').on('click', function () { $('#codeOutput').empty(); var js = myCodeMirror.getValue(); - submit(js); + js = scrapeTests(js); + submit(js, function(cls, message) { + if (cls) { + codeOutput.setValue(message.error); + runTests('Error', null); + } else { + codeOutput.setValue(message.output); + runTests(null, message, function() { + createTestDisplay(); + }); + } + }); }); -var assert = chai.assert; -var testResults = []; -$('#runTests').on('click', function () { - clearTestOutput(); - var testCaseList = [], - jsCode = myCodeMirror.getValue(); - getTestSuite().each(function () { - testCaseList.push([$(this).data("input"), $(this).data("output"), $(this)]); - }); - testCaseList.forEach(function (input) { - var testCode = jsCode + "\n\n" + input[0] + ";"; - //TODO use plugin for this with the rest as a callback? - var output = eval(testCode); - testEquality(output, input); - }); - // some timeout here? - if (testResults.length === testCaseList.length) { - var sum = testResults.reduce(function (a, b) { - return a + b - }); - prependTestOutput("======Testing========\n" + Math.round(100 * sum / testResults.length) + "% tests passed\n"); - } -}); -var testEquality = function (output, input) { - try { - switch (typeof output) { - case 'object': - assert.deepEqual(output, input[1]); - break; - case 'string': - assert(output.localeCompare(input[1])); - break - default: - assert.equal(output, input[1]); +var tests; +var testSalt = Math.Random(); +var scrapeTests = function(js) { + return js; +} + +var createTestDisplay = function() { + tests.forEach(function(test) { + var testDoc = document.createElement("li"); + $(testDoc) + .addClass('list-group-item') + .addClass('well') + .addClass('well-sm') + .html(test.text); + if (failedTests.indexOf(test) > -1) { + $(testDoc) + .css("background-color", 'rgba(255,0,0,.2)') + .prependTo($('#testSuite')); + } else { + $(testDoc) + .css('background-color', 'rgba(0,255,0,.2)') + .appendTo($('#testSuite')); } - appendTestOutput("\n" + createTestString(input[0], input[1]) + "\nTest passed!\n"); - input[2].css("background-color", "rgba(0,255,0,.2)"); - testResults.push(1); - } catch (err) { - input[2].css("background-color", "rgba(255,0,0,.2)"); - appendTestOutput(createTestString(input[0], input[1])); - appendTestOutput("Test failed: \nOutput was: " + output + "\nType of output was: " + (typeof output)); - testResults.push(0); + }); +} + +var testWords = ['expect', 'should', 'assert']; +var failedTests = []; + +var runTests = function(err, data, callback) { + if (err && tests) { + tests = [{text: "No tests were run as the program returned an error"}]; + createTestDisplay(); + return; + } else if (tests) { + $('#testSuite').children().remove(); + tests.forEach(function(test){ + var testString = reassembleTest(test, data); + try { + var output = eval(testString); + } catch(err) { + failedTests.push(test); + } + }); + callback(); } -}; +} + + +var reassembleTest = function(test, data) { + var lineNum = test.line; + var regexp = new RegExp("\/\/" + lineNum + testSalt); + return data.input.replace(regexp, test.text); +} + +// var assert = chai.assert; +// var testResults = []; +// $('#runTests').on('click', function () { +// clearTestOutput(); +// var testCaseList = [], +// jsCode = myCodeMirror.getValue(); +// getTestSuite().each(function () { +// testCaseList.push([$(this).data("input"), $(this).data("output"), $(this)]); +// }); +// testCaseList.forEach(function (input) { +// var testCode = jsCode + "\n\n" + input[0] + ";"; +// //TODO use plugin for this with the rest as a callback? +// var output = eval(testCode); +// testEquality(output, input); +// }); +// // some timeout here? +// if (testResults.length === testCaseList.length) { +// var sum = testResults.reduce(function (a, b) { +// return a + b +// }); +// prependTestOutput("======Testing========\n" + Math.round(100 * sum / testResults.length) + "% tests passed\n"); +// } +// }); +// var testEquality = function (output, input) { +// try { +// switch (typeof output) { +// case 'object': +// assert.deepEqual(output, input[1]); +// break; +// case 'string': +// assert(output.localeCompare(input[1])); +// break +// default: +// assert.equal(output, input[1]); +// } +// appendTestOutput("\n" + createTestString(input[0], input[1]) + "\nTest passed!\n"); +// input[2].css("background-color", "rgba(0,255,0,.2)"); +// testResults.push(1); +// } catch (err) { +// input[2].css("background-color", "rgba(255,0,0,.2)"); +// appendTestOutput(createTestString(input[0], input[1])); +// appendTestOutput("Test failed: \nOutput was: " + output + "\nType of output was: " + (typeof output)); +// testResults.push(0); +// } +// }; //$('#sideBySide').on('click', function () { // var main = $('#mainEditorPanel'); // if (main.hasClass('col-md-12')) { @@ -214,57 +281,57 @@ var writeToTest = function (msg, location) { // m = re.exec(code); // } //}; -$('#testFunctionName').on('change', function () { - $('#testInputs').children().remove(); - $('#testOutputs').children().remove(); - var args = $('#testFunctionName option:selected').data("args"); - var argArray = args.split(","); - argArray.forEach(function (arg) { - if (arg.length > 0) { - createInputField('#testInputs', arg); - } - }); - createInputField('#testOutputs', 'Expected output'); -}); -var createInputField = function (className, arg) { - var inputDiv = document.createElement('div'); - $(inputDiv) - .addClass("control-group") - .appendTo($(className)); - var inputLabel = document.createElement('label'); - $(inputLabel) - .attr("for", "inputs") - .html(arg) - .addClass("col-xs-4 control-label") - .appendTo($(inputDiv)); - var textDiv = document.createElement('div'); - $(textDiv) - .addClass("col-xs-8 controls") - .appendTo($(inputDiv)); - var inputArea = document.createElement('input'); - $(inputArea) - .attr("type", "text") - .addClass("form-control") - .appendTo($(inputDiv)); - $(document.createElement("br")).appendTo($(textDiv)); -}; -$('#testFunctionName').on('focus', function () { - $('#testFunctionName').children().remove(); - var blankOpt = document.createElement("option"); - $(blankOpt).addClass("selected").appendTo($('#testFunctionName')); - var re = /function\s+(\w+)\s*\(([\w\s,]*)\)/g; - var code = myCodeMirror.getValue(); - createOptions(re, code); - re = /var (\w+)\s*=\s*function\s*\(([\s\w,]*)\)/g; - createOptions(re, code); -}); -$('#hideTestCreate').on('click', function () { - var testForm = $("#testCreateForm"); - if (testForm.is(":visible")) { - testForm.hide(); - $(this).text("Create more tests"); - } else { - testForm.show(); - $(this).text("Hide test creation dialogue") - } -}); \ No newline at end of file +// $('#testFunctionName').on('change', function () { +// $('#testInputs').children().remove(); +// $('#testOutputs').children().remove(); +// var args = $('#testFunctionName option:selected').data("args"); +// var argArray = args.split(","); +// argArray.forEach(function (arg) { +// if (arg.length > 0) { +// createInputField('#testInputs', arg); +// } +// }); +// createInputField('#testOutputs', 'Expected output'); +// }); +// var createInputField = function (className, arg) { +// var inputDiv = document.createElement('div'); +// $(inputDiv) +// .addClass("control-group") +// .appendTo($(className)); +// var inputLabel = document.createElement('label'); +// $(inputLabel) +// .attr("for", "inputs") +// .html(arg) +// .addClass("col-xs-4 control-label") +// .appendTo($(inputDiv)); +// var textDiv = document.createElement('div'); +// $(textDiv) +// .addClass("col-xs-8 controls") +// .appendTo($(inputDiv)); +// var inputArea = document.createElement('input'); +// $(inputArea) +// .attr("type", "text") +// .addClass("form-control") +// .appendTo($(inputDiv)); +// $(document.createElement("br")).appendTo($(textDiv)); +// }; +// $('#testFunctionName').on('focus', function () { +// $('#testFunctionName').children().remove(); +// var blankOpt = document.createElement("option"); +// $(blankOpt).addClass("selected").appendTo($('#testFunctionName')); +// var re = /function\s+(\w+)\s*\(([\w\s,]*)\)/g; +// var code = myCodeMirror.getValue(); +// createOptions(re, code); +// re = /var (\w+)\s*=\s*function\s*\(([\s\w,]*)\)/g; +// createOptions(re, code); +// }); +// $('#hideTestCreate').on('click', function () { +// var testForm = $("#testCreateForm"); +// if (testForm.is(":visible")) { +// testForm.hide(); +// $(this).text("Create more tests"); +// } else { +// testForm.show(); +// $(this).text("Hide test creation dialogue") +// } +// }); \ No newline at end of file diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 4463d7656d..6e448e63ae 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -28,16 +28,14 @@ block content .panel.panel-primary.panel-bonfire .panel-heading.text-center Output .panel.panel-body - ul#testSuite.list-group - #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) - form#testCreateForm.form-horizontal + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) br form.code .form-group.codeMirrorView textarea#codeOutput + br + ul#testSuite.list-group - #testInputs.form-group - #testOutputs.form-group br script(src='/js/lib/bonfire/framework.js') From 6a15e4e8f837e38f1acf94eedab58476a44dc4d5 Mon Sep 17 00:00:00 2001 From: jmcshane Date: Wed, 21 Jan 2015 01:48:48 -0500 Subject: [PATCH 10/13] Finalizing code after testing --- public/js/lib/bonfire/framework.js | 125 +++++++++++++++++++---------- 1 file changed, 83 insertions(+), 42 deletions(-) diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js index 8a7abbfcdf..5407a4e1ab 100644 --- a/public/js/lib/bonfire/framework.js +++ b/public/js/lib/bonfire/framework.js @@ -23,10 +23,13 @@ var editor = myCodeMirror; myCodeMirror.setValue('/*Welcome to Bonfire, Free Code Camp\'s future CoderByte replacement.\n' + 'Please feel free to use Bonfire as an in-browser playground and linting tool.*/\n\n\n' + 'function test() {\n' + +' assert(2 === 3, "hello");\n' + ' return [1,2,3].map(function(elem) {\n' + ' return elem * elem;\n' + ' });\n' + -'}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + +'}\n' + +'expect(test()).to.be.a("array");\n\n' + +'assert.deepEqual(test(), [1,4,9]);' + 'test();'); //myCodeMirror.setSize("100%", "100%"); @@ -68,78 +71,116 @@ var doLinting = function () { } }); }; -var assert = chai.assert; + +var replaceQuotesInTests = function() { + tests.forEach(function(elt, ix, arr) { + arr[ix].text = arr[ix].text.replace(/\"/g,'\''); + }); +}; + +var tests; +var testWords = ["expect", "assert"]; +var testSalt = Math.random(); + +var scrapeTests = function(js) { + var counter = 0; + for (var i = 0; i < testWords.length; i++) { + var regex = new RegExp(testWords[i] + "[^;]+;",'g'); + var match = regex.exec(js); + while (match != null) { + var replacement = '//' + counter + testSalt; + js = js.substring(0, match.index) + + replacement + + js.substring(match.index + match[0].length); + + if (!tests) tests = []; + tests.push({"text": match[0], "line": counter, "err": null}); + counter++; + match = regex.exec(js); + } + } + replaceQuotesInTests(); + return js; +}; + $('#submitButton').on('click', function () { + tests = undefined; $('#codeOutput').empty(); var js = myCodeMirror.getValue(); js = scrapeTests(js); + console.log(js); submit(js, function(cls, message) { if (cls) { codeOutput.setValue(message.error); runTests('Error', null); } else { codeOutput.setValue(message.output); - runTests(null, message, function() { - createTestDisplay(); - }); + runTests(null, message); } }); }); -var tests; -var testSalt = Math.Random(); -var scrapeTests = function(js) { - return js; -} - +var pushed = false; var createTestDisplay = function() { - tests.forEach(function(test) { + if (pushed) { + tests.pop(); + } + console.log(tests); + for (var i = 0; i < tests.length;i++) { + var test = tests[i]; var testDoc = document.createElement("li"); $(testDoc) .addClass('list-group-item') - .addClass('well') + .addClass('well img-rounded') .addClass('well-sm') - .html(test.text); - if (failedTests.indexOf(test) > -1) { + if (test.err != null) { $(testDoc) + .html(test.text + "\n" + test.err) .css("background-color", 'rgba(255,0,0,.2)') .prependTo($('#testSuite')); } else { $(testDoc) + .html(test.text) .css('background-color', 'rgba(0,255,0,.2)') .appendTo($('#testSuite')); } - }); -} - -var testWords = ['expect', 'should', 'assert']; -var failedTests = []; - -var runTests = function(err, data, callback) { - if (err && tests) { - tests = [{text: "No tests were run as the program returned an error"}]; - createTestDisplay(); - return; - } else if (tests) { - $('#testSuite').children().remove(); - tests.forEach(function(test){ - var testString = reassembleTest(test, data); - try { - var output = eval(testString); - } catch(err) { - failedTests.push(test); - } - }); - callback(); - } -} - - + }; +}; +var assert = chai.assert; +var expect = chai.expect; var reassembleTest = function(test, data) { var lineNum = test.line; var regexp = new RegExp("\/\/" + lineNum + testSalt); return data.input.replace(regexp, test.text); -} +}; +var runTests = function(err, data) { + pushed = false; + $('#testSuite').children().remove(); + if (err && tests) { + tests = [{text:"Program Execution Failure", err: "No tests were run."}]; + createTestDisplay(); + } else if (tests) { + tests.push(false); + pushed = true; + tests.forEach(function(test, ix, arr){ + try { + if (test) { + var output = eval(reassembleTest(test, data)); + } + } catch(error) { + console.log(error); + arr[ix].err = error.name + ":" + error.message; + console.log(arr); + } finally { + if (!test) { + //window.setTimeout(function() {createTestDisplay()},2000); + createTestDisplay(); + } + } + }); + } +}; + // var assert = chai.assert; // var testResults = []; From 62648a4103b19c7c3b8015d5c9741b04b4dce31c Mon Sep 17 00:00:00 2001 From: jmcshane Date: Wed, 21 Jan 2015 01:49:50 -0500 Subject: [PATCH 11/13] Cleanup after final changes --- public/js/lib/bonfire/framework.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js index 5407a4e1ab..cd60546f2f 100644 --- a/public/js/lib/bonfire/framework.js +++ b/public/js/lib/bonfire/framework.js @@ -29,7 +29,7 @@ myCodeMirror.setValue('/*Welcome to Bonfire, Free Code Camp\'s future CoderByte ' });\n' + '}\n' + 'expect(test()).to.be.a("array");\n\n' + -'assert.deepEqual(test(), [1,4,9]);' + +'assert.deepEqual(test(), [1,4,9]);\n\n' + 'test();'); //myCodeMirror.setSize("100%", "100%"); From c422f9474be5b5c2a99dcd765c2ac0c60f6df15b Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Wed, 21 Jan 2015 17:17:45 -0500 Subject: [PATCH 12/13] More improvements, keyboard shortcuts working again, extra protection against fatal errors --- controllers/bonfire.js | 1 + public/js/lib/bonfire/bonfire.js | 1 + public/js/lib/bonfire/framework.js | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/controllers/bonfire.js b/controllers/bonfire.js index 86421feeaa..a4fbabad51 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -7,6 +7,7 @@ var _ = require('lodash'), */ exports.index = function(req, res) { res.render('bonfire/bonfire.jade', { + title: 'Learn to code with Bonfire - Free Code Camp' }); //Bonfire.find({}, null, { sort: { bonfireNumber: 1 } }, function(err, c) { // if (err) { diff --git a/public/js/lib/bonfire/bonfire.js b/public/js/lib/bonfire/bonfire.js index 720adb22c9..19be8fb116 100644 --- a/public/js/lib/bonfire/bonfire.js +++ b/public/js/lib/bonfire/bonfire.js @@ -43,6 +43,7 @@ var api = { //print('input', data.input); if (data.error) { print('Error', data); + reset(); } else { print(null, data); } diff --git a/public/js/lib/bonfire/framework.js b/public/js/lib/bonfire/framework.js index cd60546f2f..726894d387 100644 --- a/public/js/lib/bonfire/framework.js +++ b/public/js/lib/bonfire/framework.js @@ -14,7 +14,7 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor") onKeyEvent: doLinting, extraKeys : { "Ctrl-Enter" : function() { - submit(myCodeMirror.getValue()); + bonfireExecute(); return false; } } @@ -104,6 +104,10 @@ var scrapeTests = function(js) { }; $('#submitButton').on('click', function () { + bonfireExecute(); +}); + +function bonfireExecute() { tests = undefined; $('#codeOutput').empty(); var js = myCodeMirror.getValue(); @@ -118,7 +122,7 @@ $('#submitButton').on('click', function () { runTests(null, message); } }); -}); +} var pushed = false; var createTestDisplay = function() { From bf1d8cd81acfe03ec50466f8511d2b41716b5d92 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Wed, 21 Jan 2015 18:10:21 -0500 Subject: [PATCH 13/13] Improving bonfire layout to remove the need for scrolling and allow the input and output elements to grow dynamically with user input --- public/css/main.less | 1 + public/js/lib/codemirror/lib/codemirror.css | 1 + public/js/lib/codemirror/theme/monokai.css | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/css/main.less b/public/css/main.less index a4492ed832..22340bab55 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -530,6 +530,7 @@ form.code span { font-family: "Ubuntu Mono"; padding-bottom: 0px; margin-bottom: 0px; + height: auto; } #mainEditorPanel .panel-body { diff --git a/public/js/lib/codemirror/lib/codemirror.css b/public/js/lib/codemirror/lib/codemirror.css index cb705fa0a2..4e479843c8 100644 --- a/public/js/lib/codemirror/lib/codemirror.css +++ b/public/js/lib/codemirror/lib/codemirror.css @@ -147,6 +147,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror-scroll { + /* Commenting out for no scroll display overflow: scroll !important; /* Things will break if this is overridden */ /* 30px is the magic margin used to hide the element's real scrollbars */ /* See overflow: hidden in .CodeMirror */ diff --git a/public/js/lib/codemirror/theme/monokai.css b/public/js/lib/codemirror/theme/monokai.css index 74c3d2d903..2aa742c4ca 100644 --- a/public/js/lib/codemirror/theme/monokai.css +++ b/public/js/lib/codemirror/theme/monokai.css @@ -1,6 +1,6 @@ /* Based on Sublime Text's Monokai theme */ -.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2; border-radius: 5px;} +.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2; border-radius: 5px; height: auto;} .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} .cm-s-monokai .CodeMirror-guttermarker { color: white; }