courseware testing implemented
This commit is contained in:
@ -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(),
|
||||
|
231
public/js/lib/chai/chai-jquery.js
Normal file
231
public/js/lib/chai/chai-jquery.js
Normal 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);
|
||||
};
|
||||
}
|
||||
);
|
||||
}));
|
@ -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';
|
@ -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>"
|
||||
}
|
||||
]
|
@ -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
|
||||
// = 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
|
||||
= phrase
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user