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),
tests: courseware.tests,
challengeSeed: courseware.challengeSeed,
challengeEntryPoint: courseware.challengeEntryPoint,
cc: !!req.user,
points: req.user ? req.user.points : undefined,
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");
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 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>";
"<script>document.domain = 'localhost'</script>" +
"<script src='/js/lib/chai/chai.js'></script>" +
"<script src='/js/lib/chai/chai-jquery.js'></script>" +
"<script src='//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js'></script>" +
"<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;
// Initialize CodeMirror editor with a nice html5 canvas demo.
@ -38,32 +54,33 @@ editor.on("change", function () {
clearTimeout(delay);
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() {
var previewFrame = document.getElementById('preview');
var preview = previewFrame.contentDocument || previewFrame.contentWindow.document;
preview.open();
preview.write(libraryIncludes + editor.getValue());
preview.write(libraryIncludes + editor.getValue() + coursewareTests);
preview.close();
var passing = true;
}
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"), {
lineNumbers: false,
mode: "text",
@ -86,17 +103,9 @@ var editorValue;
var challengeSeed = challengeSeed || null;
var tests = tests || [];
var challengeEntryPoint = challengeEntryPoint || null;
if (challengeSeed !== null) {
editorValue = challengeSeed + '\n\n' + challengeEntryPoint;
} else {
editorValue = nonChallengeValue;
}
myCodeMirror.setValue(editorValue);
myCodeMirror.setValue(challengeSeed);
function doLinting () {
editor.operation(function () {
@ -131,10 +140,7 @@ function bonfireExecute() {
var userJavaScript = myCodeMirror.getValue();
userJavaScript = removeComments(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) {
if (cls) {
codeOutput.setValue(message.error);
@ -251,4 +257,6 @@ var runTests = function(err, data) {
function showCompletion() {
$('#complete-bonfire-dialog').modal('show');
}
}
document.domain = 'localhost';

View File

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

View File

@ -7,6 +7,7 @@ block content
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')
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/addon/lint/lint.css')
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
@ -63,46 +64,40 @@ block content
.hidden-xs.hidden-sm
img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png")
iframe.iphone#preview
//
.visible-xs-block.visible-sm-block.text-center
iframe.no-phone#preview
//
//.modal-dialog.animated.zoomIn.fast-animation
// .modal-content
// .modal-header.challenge-list-header= compliment
// a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
// .modal-body(ng-controller="pairedWithController")
// .text-center
// .animated.zoomInDown.delay-half
// span.landing-icon.ion-checkmark-circled.text-primary
// - if (cc)
// form.form-horizontal(novalidate='novalidate', name='completedWithForm')
// .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
// // extra field to distract password tools like lastpass from injecting css into our username field
// 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)
// .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")
// alert(type='danger')
// span.ion-close-circled
// | 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-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
// - else
// a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
// #all-bonfires-dialog.modal(tabindex='-1')
//.modal-dialog.animated.fadeInUp.fast-animation
// .modal-content
// .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")
#complete-bonfire-dialog.modal(tabindex='-1')
.modal-dialog.animated.zoomIn.fast-animation
.modal-content
.modal-header.challenge-list-header= compliment
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
.modal-body(ng-controller="pairedWithController")
.text-center
.animated.zoomInDown.delay-half
span.completion-icon.ion-checkmark-circled.text-primary
- if (cc)
form.form-horizontal(novalidate='novalidate', name='completedWithForm')
.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
// extra field to distract password tools like lastpass from injecting css into our username field
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)
.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')
span.ion-close-circled
| 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-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
- else
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress