From 6b4475a5b87b358b4a00904ef2e81d2ed0bdd49c Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Sat, 24 Jan 2015 00:44:08 -0500 Subject: [PATCH] Major refactor of bonfireFramework to differentiate tests, refactor tests in Bonfire schema to remove public/private tests and only have tests, fire modal on successful run of all tests in bonfire/show, added bonfiresHash to User schema, and we should have committed a while ago because there is too much too list --- app.js | 14 ++ controllers/bonfire.js | 13 +- models/Bonfire.js | 4 +- models/BonfireCompletion.js | 7 +- models/User.js | 243 +++++++++++++++++++++- public/js/lib/bonfire/bonfireFramework.js | 73 ++++--- public/js/main.js | 86 +++++--- seed_data/bonfires.json | 14 +- seed_data/challenge-hashes | 4 +- views/bonfire/show.jade | 31 ++- views/challenges/show.jade | 2 +- views/partials/bonfires.jade | 8 +- 12 files changed, 401 insertions(+), 98 deletions(-) diff --git a/app.js b/app.js index 5b32f9b1af..bbae2fd4ba 100644 --- a/app.js +++ b/app.js @@ -285,6 +285,20 @@ app.post('/completed-challenge', function (req, res) { req.user.save(); }); +app.post('/completed-bonfire/', function (req, res) { + req.user.challengesHash[parseInt(req.body.challengeNumber)] = + Math.round(+new Date() / 1000); + var ch = req.user.challengesHash; + var p = 0; + for (var k in ch) { + if (ch[k] > 0) { + p += 1; + } + } + req.user.points = p; + req.user.save(); +}); + /** * OAuth sign-in routes. */ diff --git a/controllers/bonfire.js b/controllers/bonfire.js index cf7f80eb4e..958663f6e4 100644 --- a/controllers/bonfire.js +++ b/controllers/bonfire.js @@ -62,16 +62,13 @@ exports.returnBonfire = function(req, res, next) { number: bonfire[bonfireNumber].bonfireNumber, difficulty: bonfire[bonfireNumber].difficulty, description: bonfire[bonfireNumber].description, - publicTests: bonfire[bonfireNumber].publicTests, - privateTests: bonfire[bonfireNumber].privateTests, + tests: bonfire[bonfireNumber].tests, challengeSeed: bonfire[bonfireNumber].challengeSeed, challengeEntryPoint: bonfire[bonfireNumber].challengeEntryPoint, - challengeEntryPointNegate: bonfire[bonfireNumber].challengeEntryPointNegate, - - //cc: req.user ? req.user.bonfiresHash : undefined, - //points: req.user ? req.user.points : undefined, - //verb: verbs[Math.floor(Math.random() * verbs.length)], - //phrase: phrases[Math.floor(Math.random() * phrases.length)], + cc: req.user ? req.user.bonfiresHash : undefined, + points: req.user ? req.user.points : undefined, + verb: verbs[Math.floor(Math.random() * verbs.length)], + phrase: phrases[Math.floor(Math.random() * phrases.length)], bonfires: bonfire }); }); diff --git a/models/Bonfire.js b/models/Bonfire.js index 461e663d77..dd0cff31d7 100644 --- a/models/Bonfire.js +++ b/models/Bonfire.js @@ -8,15 +8,13 @@ var secrets = require('../config/secrets'); var bonfireSchema = new mongoose.Schema({ - name: { type: String, unique: true }, difficulty: Number, description: Array, - publicTests: Array, - privateTests: Array, + tests: Array, challengeSeed: String, bonfireNumber: Number, challengeEntryPoint: String, diff --git a/models/BonfireCompletion.js b/models/BonfireCompletion.js index 671c7ff4d9..eb4249400a 100644 --- a/models/BonfireCompletion.js +++ b/models/BonfireCompletion.js @@ -3,11 +3,8 @@ var secrets = require('../config/secrets'); var bonfireCompletionSchema = new mongoose.Schema({ dateCompleted: Number, - completedWith: String, - bonfireNumber: { - bonfireNumber: Number, - bonfireId: String - }, + completedWith: ObjectId, + bonfireHash: ObjectId, solution: String }); diff --git a/models/User.js b/models/User.js index 582bd551e5..81a33ef72d 100644 --- a/models/User.js +++ b/models/User.js @@ -353,7 +353,248 @@ var userSchema = new mongoose.Schema({ }, resetPasswordToken: String, resetPasswordExpires: Date, - bonfires: Array + bonfiresHash: { + 0: { + type: Boolean, + default: 0 + }, + 1: { + type: Boolean, + default: 0 + }, + 2: { + type: Boolean, + default: 0 + }, + 3: { + type: Boolean, + default: 0 + }, + 4: { + type: Boolean, + default: 0 + }, + 5: { + type: Boolean, + default: 0 + }, + 6: { + type: Boolean, + default: 0 + }, + 7: { + type: Boolean, + default: 0 + }, + 8: { + type: Boolean, + default: 0 + }, + 9: { + type: Boolean, + default: 0 + }, + 10: { + type: Boolean, + default: 0 + }, + 11: { + type: Boolean, + default: 0 + }, + 12: { + type: Boolean, + default: 0 + }, + 13: { + type: Boolean, + default: 0 + }, + 14: { + type: Boolean, + default: 0 + }, + 15: { + type: Boolean, + default: 0 + }, + 16: { + type: Boolean, + default: 0 + }, + 17: { + type: Boolean, + default: 0 + }, + 18: { + type: Boolean, + default: 0 + }, + 19: { + type: Boolean, + default: 0 + }, + 20: { + type: Boolean, + default: 0 + }, + 21: { + type: Boolean, + default: 0 + }, + 22: { + type: Boolean, + default: 0 + }, + 23: { + type: Boolean, + default: 0 + }, + 24: { + type: Boolean, + default: 0 + }, + 25: { + type: Boolean, + default: 0 + }, + 26: { + type: Boolean, + default: 0 + }, + 27: { + type: Boolean, + default: 0 + }, + 28: { + type: Boolean, + default: 0 + }, + 29: { + type: Boolean, + default: 0 + }, + 30: { + type: Boolean, + default: 0 + }, + 31: { + type: Boolean, + default: 0 + }, + 32: { + type: Boolean, + default: 0 + }, + 33: { + type: Boolean, + default: 0 + }, + 34: { + type: Boolean, + default: 0 + }, + 35: { + type: Boolean, + default: 0 + }, + 36: { + type: Boolean, + default: 0 + }, + 37: { + type: Boolean, + default: 0 + }, + 38: { + type: Boolean, + default: 0 + }, + 39: { + type: Boolean, + default: 0 + }, + 40: { + type: Boolean, + default: 0 + }, + 41: { + type: Boolean, + default: 0 + }, + 42: { + type: Boolean, + default: 0 + }, + 43: { + type: Boolean, + default: 0 + }, + 44: { + type: Boolean, + default: 0 + }, + 45: { + type: Boolean, + default: 0 + }, + 46: { + type: Boolean, + default: 0 + }, + 47: { + type: Boolean, + default: 0 + }, + 48: { + type: Boolean, + default: 0 + }, + 49: { + type: Boolean, + default: 0 + }, + 50: { + type: Boolean, + default: 0 + }, + 51: { + type: Boolean, + default: 0 + }, + 52: { + type: Boolean, + default: 0 + }, + 53: { + type: Boolean, + default: 0 + }, + 54: { + type: Boolean, + default: 0 + }, + 55: { + type: Boolean, + default: 0 + }, + 56: { + type: Boolean, + default: 0 + }, + 57: { + type: Boolean, + default: 0 + }, + 58: { + type: Boolean, + default: 0 + }, + 59: { + type: Boolean, + default: 0 + } + } }); /** diff --git a/public/js/lib/bonfire/bonfireFramework.js b/public/js/lib/bonfire/bonfireFramework.js index 4311a7fa09..fe844286de 100644 --- a/public/js/lib/bonfire/bonfireFramework.js +++ b/public/js/lib/bonfire/bonfireFramework.js @@ -45,9 +45,9 @@ var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), lineWrapping: true }); codeOutput.setValue('/**\n' + - ' * Your output will go here. Console.log() -type statements\n' + - ' * will appear in your browser\'s javascript console.\n' + - ' */'); +' * Your output will go here. Console.log() -type statements\n' + +' * will appear in your browser\'s javascript console.\n' + +' */'); codeOutput.setSize("100%", "100%"); var info = editor.getScrollInfo(); var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; @@ -58,10 +58,8 @@ var editorValue; var challengeSeed = challengeSeed || null; -var publicTests = publicTests || []; -var privateTests = privateTests || []; +var tests = tests || []; var challengeEntryPoint = challengeEntryPoint || null; -var challengeEntryPointNegate = challengeEntryPointNegate || null; if (challengeSeed !== null) { @@ -101,7 +99,7 @@ $('#submitButton').on('click', function () { }); function bonfireExecute() { - tests = null; + userTests= null; $('#codeOutput').empty(); var userJavaScript = myCodeMirror.getValue(); userJavaScript = removeComments(userJavaScript); @@ -119,23 +117,20 @@ function bonfireExecute() { } var replaceQuotesInTests = function() { - tests.forEach(function(elt, ix, arr) { + userTests.forEach(function(elt, ix, arr) { arr[ix].text = arr[ix].text.replace(/\"/g,'\''); }); }; -var tests; +var userTests; var testSalt = Math.random(); var scrapeTests = function(userJavaScript) { - for (var i = 0; i < publicTests.length; i++) { - userJavaScript += '\n' + publicTests[i]; - } - - for (var i = 0; i < privateTests.length; i++) { - userJavaScript += '\n' + privateTests[i]; + // insert tests from mongo + for (var i = 0; i < tests.length; i++) { + userJavaScript += '\n' + tests[i]; } var counter = 0; @@ -144,16 +139,20 @@ var scrapeTests = function(userJavaScript) { while (match != null) { var replacement = '//' + counter + testSalt; userJavaScript = userJavaScript.substring(0, match.index) - + replacement - + userJavaScript.substring(match.index + match[0].length); + + replacement + + userJavaScript.substring(match.index + match[0].length); - if (!tests) tests = []; - tests.push({"text": match[0], "line": counter, "err": null}); + if (!userTests) { + userTests= []; + } + userTests.push({"text": match[0], "line": counter, "err": null}); counter++; match = regex.exec(userJavaScript); } - if (tests) replaceQuotesInTests(); + if (userTests) { + replaceQuotesInTests(); + } return userJavaScript; }; @@ -170,10 +169,10 @@ function removeLogs(userJavaScript) { var pushed = false; var createTestDisplay = function() { if (pushed) { - tests.pop(); + userTests.pop(); } - for (var i = 0; i < tests.length;i++) { - var test = tests[i]; + for (var i = 0; i < userTests.length;i++) { + var test = userTests[i]; var testDoc = document.createElement("li"); $(testDoc) .addClass('list-group-item') @@ -201,28 +200,40 @@ var reassembleTest = function(test, data) { var regexp = new RegExp("\/\/" + lineNum + testSalt); return data.input.replace(regexp, test.text); }; + var runTests = function(err, data) { + var allTestsPassed = true; pushed = false; $('#testSuite').children().remove(); - if (err && tests.length > 0) { - tests = [{text:"Program Execution Failure", err: "No tests were run."}]; + if (err && userTests.length > 0) { + userTests= [{text:"Program Execution Failure", err: "NouserTestswere run."}]; createTestDisplay(); - } else if (tests) { - tests.push(false); + } else if (userTests) { + + userTests.push(false); pushed = true; - tests.forEach(function(test, ix, arr){ + userTests.forEach(function(test, ix, arr){ try { if (test) { var output = eval(reassembleTest(test, data)); } } catch(error) { - + allTestsPassed = false; arr[ix].err = error.name + ":" + error.message; - } finally { + } finally { if (!test) { createTestDisplay(); } } }); } -}; \ No newline at end of file + if (allTestsPassed) { + allTestsPassed = false; + showCompletion(); + } + console.log(allTestsPassed); +}; + +function showCompletion() { + $('#complete-bonfire-dialog').modal('show'); +} \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index ced2575987..60cd47f011 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,46 +1,64 @@ $(document).ready(function() { - var CSRF_HEADER = 'X-CSRF-Token'; + var CSRF_HEADER = 'X-CSRF-Token'; - var setCSRFToken = function(securityToken) { - jQuery.ajaxPrefilter(function(options, _, xhr) { - if (!xhr.crossDomain) { - xhr.setRequestHeader(CSRF_HEADER, securityToken); - } + var setCSRFToken = function(securityToken) { + jQuery.ajaxPrefilter(function(options, _, xhr) { + if (!xhr.crossDomain) { + xhr.setRequestHeader(CSRF_HEADER, securityToken); + } + }); + }; + + setCSRFToken($('meta[name="csrf-token"]').attr('content')); + + $('.start-challenge').on('click', function() { + $(this).parent().remove(); + $('.challenge-content') + .removeClass('hidden-element') + .addClass('animated fadeInDown'); }); - }; - setCSRFToken($('meta[name="csrf-token"]').attr('content')); + $('.completed-challenge').on('click', function() { + $('#complete-challenge-dialog').modal('show'); + // Only post to server if there is an authenticated user + if ($('.signup-btn-nav').length < 1) { + l = location.pathname.split('/'); + cn = l[l.length - 1]; + $.ajax({ + type: 'POST', + data: {challengeNumber: cn}, + url: '/completed-challenge/' + }); + } + }); - $('.start-challenge').on('click', function() { - $(this).parent().remove(); - $('.challenge-content') - .removeClass('hidden-element') - .addClass('animated fadeInDown'); - }); + $('.completed-bonfire').on('click', function() { + $('#complete-bonfire-dialog').modal('show'); + // Only post to server if there is an authenticated user + if ($('.signup-btn-nav').length < 1) { + l = location.pathname.split('/'); + cn = l[l.length - 1]; + $.ajax({ + type: 'POST', + data: {bonfireHash: cn}, + url: '/completed-bonfire/' + }); + } + }); - $('.completed-challenge').on('click', function() { - $('#complete-dialog').modal('show'); - // Only post to server if there is an authenticated user - if ($('.signup-btn-nav').length < 1) { - l = location.pathname.split('/'); - cn = l[l.length - 1]; - $.ajax({ - type: 'POST', - data: {challengeNumber: cn}, - url: '/completed-challenge/' - }); - } - }); + $('.all-challenges').on('click', function() { + $('#all-challenges-dialog').modal('show'); + }); - $('.all-challenges').on('click', function() { - $('#all-challenges-dialog').modal('show'); - }); + $('.all-bonfires').on('click', function() { + $('#all-bonfires-dialog').modal('show'); + }); - $('.next-button').on('click', function() { - l = location.pathname.split('/'); - window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); - }); + $('.next-button').on('click', function() { + l = location.pathname.split('/'); + window.location = '/challenges/' + (parseInt(l[l.length - 1]) + 1); + }); }); var profileValidation = angular.module('profileValidation',['ui.bootstrap']); diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index 79f5f34bfb..0a5dedfe97 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -1,5 +1,6 @@ [ { + "_id" : "aaa48de84e1ecc7c742e1124", "name": "Palindrome Tester", "difficulty": 1, "description": [ @@ -8,13 +9,11 @@ "Strings will be passed in with varying formats, such as \"racecar\", \"RaceCar\", and \"race CAR\" among others.", "Return true if the string is a palindrome, otherwise false" ], - "publicTests": [ + "tests": [ "expect(palindrome(\"eye\")).to.be.a(\"boolean\");", "assert.deepEqual(palindrome(\"eye\"), true);", "assert.deepEqual(palindrome(\"race car\"), true);", - "assert.deepEqual(palindrome(\"not a palindrome\"), false);" - ], - "privateTests": [ + "assert.deepEqual(palindrome(\"not a palindrome\"), false);", "assert.deepEqual(palindrome(\"A man, a plan, a canal. Panama\"), true);", "assert.deepEqual(palindrome(\"never odd or even\"), true);", "assert.deepEqual(palindrome(\"nope\"), false);" @@ -25,6 +24,7 @@ "challengeEntryPointNegate" : "palindrome\\([^str].*\\;" }, { + "_id" : "ff0395860f5d3034dc0bfc94", "name": "Validate US Telephone Numbers", "difficulty": 3, "description": [ @@ -32,13 +32,11 @@ "555-555-5555, (555)555-5555, (555) 555-5555, 555 555 5555, 5555555555, 1 555 555 5555", "For this challenge you will be presented with a string such as \"800-692-7753\" or \"8oo-six427676;laskdjf\". Your job is to validate or reject the US phone number based on any combination of the formats provided above. The area code is required. If the country code code is provided, you must confirm that the country code is \"1\". Return true if the string is a valid US phone number; otherwise false." ], - "publicTests": [ + "tests": [ "expect(telephoneCheck(\"555-555-5555\")).to.be.a(\"boolean\");", "assert.deepEqual(telephoneCheck(\"555-555-5555\"), true);", "assert.deepEqual(telephoneCheck(\"1 456 789 4444\"), true);", - "assert.deepEqual(telephoneCheck(\"123**&!!asdf#\"), false);" - ], - "privateTests": [ + "assert.deepEqual(telephoneCheck(\"123**&!!asdf#\"), false);", "assert.deepEqual(telephoneCheck(\"55555555\"), false);", "assert.deepEqual(telephoneCheck(\"(6505552368)\"), false);", "assert.deepEqual(telephoneCheck(\"2 (757) 622-7382\"), false);", diff --git a/seed_data/challenge-hashes b/seed_data/challenge-hashes index 8b6ddfa36d..a03c038d90 100644 --- a/seed_data/challenge-hashes +++ b/seed_data/challenge-hashes @@ -1,6 +1,6 @@ /* -"aaa48de84e1ecc7c742e1124" -"ff0395860f5d3034dc0bfc94" + + "7123c8c441eddfaeb5bdef0d" "c3a4d278b9e760a0ffe8321f" "aceca143b92049a4392a859e" diff --git a/views/bonfire/show.jade b/views/bonfire/show.jade index 6b51fe72f1..9c420e283b 100644 --- a/views/bonfire/show.jade +++ b/views/bonfire/show.jade @@ -23,7 +23,7 @@ block content .panel-heading.text-center #{name} (Level #{difficulty} bonfire) .panel.panel-body .well - .text-justify.bonfire-instructions + .text-left.bonfire-instructions for sentence in description p.bonfire-instructions!= sentence form.code @@ -42,9 +42,32 @@ block content ul#testSuite.list-group br script(type="text/javascript"). - var publicTests = !{JSON.stringify(publicTests)}; - var privateTests = !{JSON.stringify(privateTests)}; + var tests = !{JSON.stringify(tests)}; var challengeSeed = !{JSON.stringify(challengeSeed)}; var challengeEntryPoint = !{JSON.stringify(challengeEntryPoint)}; - var challengeEntryPointNegate = !{JSON.stringify(challengeEntryPointNegate)}; script(src='/js/lib/bonfire/bonfireFramework.js') + + #complete-bonfire-dialog.modal(tabindex='-1') + .modal-dialog.animated.zoomIn.fast-animation + .modal-content + .modal-header.challenge-list-header Nicely done! + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + .text-center + .animated.zoomInDown.delay-half + span.landing-icon.ion-checkmark-circled.text-primary + - if (cc) + a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-button(name='_csrf', value=_csrf, aria-hidden='true') 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}%20Free%20Code%20Camp%20Challenge%20%23#{number}:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{number}&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 Challenges + a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') × + .modal-body + include ../partials/bonfires diff --git a/views/challenges/show.jade b/views/challenges/show.jade index 36d9dc8e0d..685f9d2bc9 100644 --- a/views/challenges/show.jade +++ b/views/challenges/show.jade @@ -16,7 +16,7 @@ block content .btn.btn-primary.btn-big.btn-block.completed-challenge I've completed this challenge .ten-pixel-break .btn.btn-success.btn-big.btn-block.all-challenges Show me all the challenges - #complete-dialog.modal(tabindex='-1') + #complete-challenge-dialog.modal(tabindex='-1') .modal-dialog.animated.zoomIn.fast-animation .modal-content .modal-header.challenge-list-header Nicely done! diff --git a/views/partials/bonfires.jade b/views/partials/bonfires.jade index 1adbb74b8d..cabd9ecf93 100644 --- a/views/partials/bonfires.jade +++ b/views/partials/bonfires.jade @@ -1 +1,7 @@ -//TODO: STUFF \ No newline at end of file +h3 + ol(start='0') + for bonfire in bonfires + li + a(href="/bonfires/#{bonfire.bonfireNumber}", class="#{ (cc && cc[bonfire.bonfireNumber] > 0) ? 'strikethrough' : '' }") #{bonfire.name} + |   (Level #{bonfire.difficulty}) +a.btn.btn-lg.btn-primary.btn-block(href="/done-with-first-100-hours", class="#{ ((cc && cc[53] === 0) || (!cc)) ? 'disabled' : '' }") I'm done with all the challenges! \ No newline at end of file