diff --git a/controllers/bonfire.js b/controllers/bonfire.js index e0d0a92380..010c5ce102 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -1,5 +1,5 @@ var _ = require('lodash'), - debug = require('debug')('freecc:cntr:bonfires'); +debug = require('debug')('freecc:cntr:bonfires'); // bonfire = require('./../models/Bonfire'); /** @@ -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/css/main.less b/public/css/main.less index c3f944cd12..4d28eaebe3 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -526,9 +526,21 @@ thead { } } -form.code { - font-size: 16px; - font-family: "Courier New", "Lucida Console", monospace; +form.code span { + font-size: 14px; + font-family: "Ubuntu Mono"; + padding-bottom: 0px; + margin-bottom: 0px; + height: auto; +} + +#mainEditorPanel .panel-body { + padding-bottom: 0px; +} + +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 2ff6e70a4c..19be8fb116 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,7 +17,7 @@ var submit = function(code) { // puts the message on the terminal var print = function(cls, msg) { - codeOutput.setValue(msg); + printCallback(cls,msg); }; @@ -39,11 +40,12 @@ 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); + reset(); } else { - print('output', data.output); + print(null, data); } } }; @@ -64,7 +66,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/framework.js b/public/js/lib/bonfire/framework.js new file mode 100644 index 0000000000..726894d387 --- /dev/null +++ b/public/js/lib/bonfire/framework.js @@ -0,0 +1,382 @@ +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, + scrollbarStyle: 'null', + lineWrapping: true, + gutters: ["CodeMirror-lint-markers"], + onKeyEvent: doLinting, + extraKeys : { + "Ctrl-Enter" : function() { + bonfireExecute(); + 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' + +' assert(2 === 3, "hello");\n' + +' return [1,2,3].map(function(elem) {\n' + +' return elem * elem;\n' + +' });\n' + +'}\n' + +'expect(test()).to.be.a("array");\n\n' + +'assert.deepEqual(test(), [1,4,9]);\n\n' + +'test();'); +//myCodeMirror.setSize("100%", "100%"); + +var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { + lineNumbers: false, + mode: "text", + theme: 'monokai', + readOnly: 'nocursor', + lineWrapping: true +}); +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) + 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 + })); + } + }); +}; + +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 () { + bonfireExecute(); +}); + +function bonfireExecute() { + 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); + } + }); +} + +var pushed = false; +var createTestDisplay = function() { + 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 img-rounded') + .addClass('well-sm') + 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 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 = []; +// $('#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/bonfire/plugin.js b/public/js/lib/bonfire/plugin_v0.1.1.js similarity index 84% rename from public/js/lib/bonfire/plugin.js rename to public/js/lib/bonfire/plugin_v0.1.1.js index 7da28e47d3..3c9942617b 100644 --- a/public/js/lib/bonfire/plugin.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; } @@ -42,13 +45,6 @@ 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 application = null; return eval(code); } diff --git a/public/js/lib/codemirror/lib/codemirror.css b/public/js/lib/codemirror/lib/codemirror.css index c56510e99a..4e479843c8 100644 --- a/public/js/lib/codemirror/lib/codemirror.css +++ b/public/js/lib/codemirror/lib/codemirror.css @@ -147,11 +147,13 @@ 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 */ margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; + padding-bottom: 0px; + border-radius: 5px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; diff --git a/public/js/lib/codemirror/theme/monokai.css b/public/js/lib/codemirror/theme/monokai.css index 548d2dfff6..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;} +.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; } diff --git a/views/bonfire/bonfire.jade b/views/bonfire/bonfire.jade index 0bfea95f67..6e448e63ae 100644 --- a/views/bonfire/bonfire.jade +++ b/views/bonfire/bonfire.jade @@ -10,309 +10,32 @@ 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') .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 + #submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter) + 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 + br ul#testSuite.list-group - 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, - 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; - $('#runTests').on('click', function () { - clearTestOutput(); - var testCaseList = [], - testResults = [], - 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); - 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"); - } - }); - }); - //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); - } - }; - var setTestBackground = function (elt, result) { - if (result.localeCompare('failed')) { - elt.css("background-color", "rgba(255,0,0,.2)"); - } else { - elt.css("background-color", "rgba(0,255,0,.2)"); - } - }; - 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) { - } 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 && currentState.length === 0) { - 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 + + br + + script(src='/js/lib/bonfire/framework.js')