extends ../layout block content script(src='/js/lib/codemirror/lib/codemirror.js') script(src='/js/lib/codemirror/addon/edit/closebrackets.js') script(src='/js/lib/codemirror/addon/edit/matchbrackets.js') script(src='/js/lib/codemirror/addon/lint/lint.js') script(src='/js/lib/codemirror/addon/lint/javascript-lint.js') script(src='//ajax.aspnetcdn.com/ajax/jshint/r07/jshint.js') script(src='/js/lib/chai/chai.js') 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') 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 .panel-heading.text-center Bonfire Playground .panel.panel-body form.code .form-group.codeMirrorView textarea#codeEditor(autofocus=true) 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 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") } });