courseware testing implemented

This commit is contained in:
Nathan Leniz
2015-02-04 19:59:51 -05:00
parent c487ddf18e
commit 161582f6c9
5 changed files with 318 additions and 87 deletions

View File

@ -66,7 +66,6 @@ exports.returnIndividualCourseware = function(req, res, next) {
details: courseware.description.slice(1), details: courseware.description.slice(1),
tests: courseware.tests, tests: courseware.tests,
challengeSeed: courseware.challengeSeed, challengeSeed: courseware.challengeSeed,
challengeEntryPoint: courseware.challengeEntryPoint,
cc: !!req.user, cc: !!req.user,
points: req.user ? req.user.points : undefined, points: req.user ? req.user.points : undefined,
verb: resources.randomVerb(), verb: resources.randomVerb(),

View File

@ -0,0 +1,231 @@
(function (chaiJquery) {
// Module systems magic dance.
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// NodeJS
module.exports = chaiJquery;
} else if (typeof define === "function" && define.amd) {
// AMD
define(['jquery'], function ($) {
return function (chai, utils) {
return chaiJquery(chai, utils, $);
};
});
} else {
// Other environment (usually <script> tag): plug in to global chai instance directly.
chai.use(function (chai, utils) {
return chaiJquery(chai, utils, jQuery);
});
}
}(function (chai, utils, $) {
var inspect = utils.inspect,
flag = utils.flag;
$ = $ || jQuery;
var setPrototypeOf = '__proto__' in Object ?
function (object, prototype) {
object.__proto__ = prototype;
} :
function (object, prototype) {
var excludeNames = /^(?:length|name|arguments|caller)$/;
function copyProperties(dst, src) {
Object.getOwnPropertyNames(src).forEach(function (name) {
if (!excludeNames.test(name)) {
Object.defineProperty(dst, name,
Object.getOwnPropertyDescriptor(src, name));
}
});
}
copyProperties(object, prototype);
copyProperties(object, Object.getPrototypeOf(prototype));
};
$.fn.inspect = function (depth) {
var el = $('<div />').append(this.clone());
if (depth !== undefined) {
var children = el.children();
while (depth-- > 0)
children = children.children();
children.html('...');
}
return el.html();
};
var props = {attr: 'attribute', css: 'CSS property', prop: 'property'};
for (var prop in props) {
(function (prop, description) {
chai.Assertion.addMethod(prop, function (name, val) {
var actual = flag(this, 'object')[prop](name);
if (!flag(this, 'negate') || undefined === val) {
this.assert(
undefined !== actual
, 'expected #{this} to have a #{exp} ' + description
, 'expected #{this} not to have a #{exp} ' + description
, name
);
}
if (undefined !== val) {
this.assert(
val === actual
, 'expected #{this} to have a ' + inspect(name) + ' ' + description + ' with the value #{exp}, but the value was #{act}'
, 'expected #{this} not to have a ' + inspect(name) + ' ' + description + ' with the value #{act}'
, val
, actual
);
}
flag(this, 'object', actual);
});
})(prop, props[prop]);
}
chai.Assertion.addMethod('data', function (name, val) {
// Work around a chai bug (https://github.com/logicalparadox/chai/issues/16)
if (flag(this, 'negate') && undefined !== val && undefined === flag(this, 'object').data(name)) {
return;
}
var assertion = new chai.Assertion(flag(this, 'object').data());
if (flag(this, 'negate'))
assertion = assertion.not;
return assertion.property(name, val);
});
chai.Assertion.addMethod('class', function (className) {
this.assert(
flag(this, 'object').hasClass(className)
, 'expected #{this} to have class #{exp}'
, 'expected #{this} not to have class #{exp}'
, className
);
});
chai.Assertion.addMethod('id', function (id) {
this.assert(
flag(this, 'object').attr('id') === id
, 'expected #{this} to have id #{exp}'
, 'expected #{this} not to have id #{exp}'
, id
);
});
chai.Assertion.addMethod('html', function (html) {
var actual = flag(this, 'object').html();
this.assert(
actual === html
, 'expected #{this} to have HTML #{exp}, but the HTML was #{act}'
, 'expected #{this} not to have HTML #{exp}'
, html
, actual
);
});
chai.Assertion.addMethod('text', function (text) {
var actual = flag(this, 'object').text();
this.assert(
actual === text
, 'expected #{this} to have text #{exp}, but the text was #{act}'
, 'expected #{this} not to have text #{exp}'
, text
, actual
);
});
chai.Assertion.addMethod('value', function (value) {
var actual = flag(this, 'object').val();
this.assert(
flag(this, 'object').val() === value
, 'expected #{this} to have value #{exp}, but the value was #{act}'
, 'expected #{this} not to have value #{exp}'
, value
, actual
);
});
chai.Assertion.addMethod('descendants', function (selector) {
this.assert(
flag(this, 'object').has(selector).length > 0
, 'expected #{this} to have #{exp}'
, 'expected #{this} not to have #{exp}'
, selector
);
});
$.each(['visible', 'hidden', 'selected', 'checked', 'enabled', 'disabled'], function (i, attr) {
chai.Assertion.addProperty(attr, function () {
this.assert(
flag(this, 'object').is(':' + attr)
, 'expected #{this} to be ' + attr
, 'expected #{this} not to be ' + attr);
});
});
chai.Assertion.overwriteProperty('exist', function (_super) {
return function () {
var obj = flag(this, 'object');
if (obj instanceof $) {
this.assert(
obj.length > 0
, 'expected ' + inspect(obj.selector) + ' to exist'
, 'expected ' + inspect(obj.selector) + ' not to exist');
} else {
_super.apply(this, arguments);
}
};
});
chai.Assertion.overwriteProperty('empty', function (_super) {
return function () {
var obj = flag(this, 'object');
if (obj instanceof $) {
this.assert(
obj.is(':empty')
, 'expected #{this} to be empty'
, 'expected #{this} not to be empty');
} else {
_super.apply(this, arguments);
}
};
});
chai.Assertion.overwriteMethod('match', function (_super) {
return function (selector) {
var obj = flag(this, 'object');
if (obj instanceof $) {
this.assert(
obj.is(selector)
, 'expected #{this} to match #{exp}'
, 'expected #{this} not to match #{exp}'
, selector
);
} else {
_super.apply(this, arguments);
}
}
});
chai.Assertion.overwriteChainableMethod('contain',
function (_super) {
return function (text) {
var obj = flag(this, 'object');
if (obj instanceof $) {
this.assert(
obj.is(':contains(\'' + text + '\')')
, 'expected #{this} to contain #{exp}'
, 'expected #{this} not to contain #{exp}'
, text);
} else {
_super.apply(this, arguments);
}
}
},
function(_super) {
return function() {
_super.call(this);
};
}
);
}));

View File

@ -26,11 +26,27 @@ var editor = myCodeMirror;
editor.setSize("100%", "auto"); editor.setSize("100%", "auto");
var libraryIncludes = "<script src='//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>" + var libraryIncludes = "<script src='//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>" +
"<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js'></script>" + "<script>document.domain = 'localhost'</script>" +
"<script src='//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js'></script>" + "<script src='/js/lib/chai/chai.js'></script>" +
"<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/>" + "<script src='/js/lib/chai/chai-jquery.js'></script>" +
"<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css'/>" + "<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js'></script>" +
"<style>body { padding: 0px 3px 0px 3px; }</style>"; "<script src='//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js'></script>" +
"<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/>" +
"<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css'/>" +
"<style>body { padding: 0px 3px 0px 3px; }</style>";
var coursewareTests = "<script>" +
"var allTestsGood = true;" +
"var expect = chai.expect; " +
"try {" +
tests[0] +
"} catch (err) {" +
"allTestsGood = false;" +
"}" +
"if (allTestsGood) {" +
"console.log('awesomeSauce');" +
"parent.postMessage('CompleteAwesomeSauce', 'http://localhost:3001'); }" +
"</script>";
var delay; var delay;
// Initialize CodeMirror editor with a nice html5 canvas demo. // Initialize CodeMirror editor with a nice html5 canvas demo.
@ -38,32 +54,33 @@ editor.on("change", function () {
clearTimeout(delay); clearTimeout(delay);
delay = setTimeout(updatePreview, 300); delay = setTimeout(updatePreview, 300);
}); });
/**
* Window postMessage receiving funtionality
*/
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen to message from child window
eventer(messageEvent,function(e) {
if (e.data === 'CompleteAwesomeSauce') {
showCompletion();
}
},false);
function updatePreview() { function updatePreview() {
var previewFrame = document.getElementById('preview'); var previewFrame = document.getElementById('preview');
var preview = previewFrame.contentDocument || previewFrame.contentWindow.document; var preview = previewFrame.contentDocument || previewFrame.contentWindow.document;
preview.open(); preview.open();
preview.write(libraryIncludes + editor.getValue()); preview.write(libraryIncludes + editor.getValue() + coursewareTests);
preview.close(); preview.close();
var passing = true;
} }
setTimeout(updatePreview, 300); setTimeout(updatePreview, 300);
// Default value for editor if one isn't provided in (i.e. a challenge)
var nonChallengeValue = '/*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' +
'Note that you can also write tests using Chai.js by using the keywords assert and expect */\n\n' +
'function test() {\n' +
' assert(2 !== 3, "2 is not equal to 3");\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' +
'var foo = test();\n' +
'foo.should.be.a("array");\n\n' +
'test();\n';
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
lineNumbers: false, lineNumbers: false,
mode: "text", mode: "text",
@ -86,17 +103,9 @@ var editorValue;
var challengeSeed = challengeSeed || null; var challengeSeed = challengeSeed || null;
var tests = tests || []; var tests = tests || [];
var challengeEntryPoint = challengeEntryPoint || null;
if (challengeSeed !== null) { myCodeMirror.setValue(challengeSeed);
editorValue = challengeSeed + '\n\n' + challengeEntryPoint;
} else {
editorValue = nonChallengeValue;
}
myCodeMirror.setValue(editorValue);
function doLinting () { function doLinting () {
editor.operation(function () { editor.operation(function () {
@ -131,10 +140,7 @@ function bonfireExecute() {
var userJavaScript = myCodeMirror.getValue(); var userJavaScript = myCodeMirror.getValue();
userJavaScript = removeComments(userJavaScript); userJavaScript = removeComments(userJavaScript);
userJavaScript = scrapeTests(userJavaScript); userJavaScript = scrapeTests(userJavaScript);
// simple fix in case the user forgets to invoke their function
if (challengeEntryPoint) {
userJavaScript = challengeEntryPoint + ' ' + userJavaScript;
}
submit(userJavaScript, function(cls, message) { submit(userJavaScript, function(cls, message) {
if (cls) { if (cls) {
codeOutput.setValue(message.error); codeOutput.setValue(message.error);
@ -251,4 +257,6 @@ var runTests = function(err, data) {
function showCompletion() { function showCompletion() {
$('#complete-bonfire-dialog').modal('show'); $('#complete-bonfire-dialog').modal('show');
} }
document.domain = 'localhost';

View File

@ -1,19 +1,17 @@
[ [
{ {
"_id" : "bd7123c8c441eddfaeb5bdef", "_id" : "bd7123c8c441eddfaeb5bdef",
"name": "Meet Bonfire", "name": "Intro",
"difficulty": "0", "difficulty": "0",
"description": [ "description": [
"Click the button below for further instructions.", "Welcome to the FCC courseware! You can click on the button below for more information",
"Your goal is to fix the failing test.", "Courseware comes loaded with Bootstrap, Jquery, and Angular. You can include more libraries by finding a cdn and including them in your html",
"First, run all the tests by clickin \"Run code\" or by pressing Control + Enter", "We hope you have fun learning!",
"The failing test is in red. Fix the code so that all tests pass. Then you can move on to the next Bonfire." "To advance to the next exercise, change the h1 tag's text to say hello world"
], ],
"tests": [ "tests": [
"expect(meetBonfire(\"test\")).to.be.a(\"boolean\");", "expect($('h1')).to.have.text('hello world');"
"expect(meetBonfire(\"test\")).to.be.true;"
], ],
"challengeSeed": "function meetBonfire(argument) {\n // Good luck!\n console.log(\"you can read this function's argument in the developer tools\", argument);\n\nreturn false;\n}\n\n", "challengeSeed": "<h1>hello you</h1>"
"challengeEntryPoint": "meetBonfire(\"You can do this!\");"
} }
] ]

View File

@ -7,6 +7,7 @@ block content
script(src='/js/lib/codemirror/addon/lint/javascript-lint.js') script(src='/js/lib/codemirror/addon/lint/javascript-lint.js')
script(src='//ajax.aspnetcdn.com/ajax/jshint/r07/jshint.js') script(src='//ajax.aspnetcdn.com/ajax/jshint/r07/jshint.js')
script(src='/js/lib/chai/chai.js') script(src='/js/lib/chai/chai.js')
script(src='/js/lib/chai/chai-jquery.js')
link(rel='stylesheet', href='/js/lib/codemirror/lib/codemirror.css') 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/addon/lint/lint.css')
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css') link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
@ -63,46 +64,40 @@ block content
.hidden-xs.hidden-sm .hidden-xs.hidden-sm
img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png") img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png")
iframe.iphone#preview iframe.iphone#preview
//
.visible-xs-block.visible-sm-block.text-center
iframe.no-phone#preview #complete-bonfire-dialog.modal(tabindex='-1')
// .modal-dialog.animated.zoomIn.fast-animation
//.modal-dialog.animated.zoomIn.fast-animation .modal-content
// .modal-content .modal-header.challenge-list-header= compliment
// .modal-header.challenge-list-header= compliment a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
// a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × .modal-body(ng-controller="pairedWithController")
// .modal-body(ng-controller="pairedWithController")
// .text-center .text-center
// .animated.zoomInDown.delay-half .animated.zoomInDown.delay-half
// span.landing-icon.ion-checkmark-circled.text-primary span.completion-icon.ion-checkmark-circled.text-primary
// - if (cc) - if (cc)
// form.form-horizontal(novalidate='novalidate', name='completedWithForm') form.form-horizontal(novalidate='novalidate', name='completedWithForm')
// .form-group.text-center .form-group.text-center
// .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2.animated.fadeIn .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2.animated.fadeIn
// // extra field to distract password tools like lastpass from injecting css into our username field // extra field to distract password tools like lastpass from injecting css into our username field
// input.form-control(ng-show="false") input.form-control(ng-show="false")
// input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser", autofocus) input.form-control#completed-with(name="existingUser", placeholder="If you paired, enter your pair's username here", existing-username='', ng-model="existingUser", autofocus)
// .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0") .col-xs-10.col-xs-offset-1.col-sm-8.col-sm-offset-2.col-md-8.col-md-offset-2(ng-cloak, ng-show="completedWithForm.$error.exists && !completedWithForm.existingUser.$pristine && existingUser.length > 0")
// alert(type='danger') alert(type='danger')
// span.ion-close-circled span.ion-close-circled
// | Username not found | Username not found
// a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, aria-hidden='true', ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Take me to my next challenge
// - if (points && points > 2) a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, aria-hidden='true', ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Take me to my next challenge
// a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank")
// i.fa.fa-twitter &nbsp;
// = phrase - if (points && points > 2)
// - else a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Bonfire:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/bonfires/#{dashedName}&hashtags=LearnToCode, JavaScript" target="_blank")
// a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress i.fa.fa-twitter &nbsp;
// #all-bonfires-dialog.modal(tabindex='-1') = phrase
//.modal-dialog.animated.fadeInUp.fast-animation - else
// .modal-content a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
// .modal-header.challenge-list-header Bonfires
// a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
// .modal-body
// include ../partials/bonfires
//
//script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js")
//script(src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js")
//style(src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css")