Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions public/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use strict';
let learnjs = {};
learnjs.problems = [
{
description: 'What is truth?',
code: 'function problem() { return __; }',
},
{
description: 'Simple Math',
code: 'function problem() { return 42 === 6 * __; }',
},
];
learnjs.appOnReady = () => {
window.onhashchange = () => {
learnjs.showView(window.location.hash);
};
learnjs.showView(window.location.hash);
};

learnjs.applyObject = function(obj, elem) {
for (let key in obj) {
elem.find('[data-name="' + key + '"]').text(obj[key]);
}
};

learnjs.problemView = (data) => {
let problemNumber = parseInt(data, 10);
let view = learnjs.template('problem-view');
let problemData = learnjs.problems[problemNumber - 1];
let resultFlash = view.find('.result');

if (problemNumber < learnjs.problems.length) {
let buttonItem = learnjs.template('skip-btn');
buttonItem.find('a').attr('href', '#problem-' + (problemNumber + 1));
$('.nav-list').append(buttonItem);
view.bind('removingView', () => {
buttonItem.remove();
});
}

let checkAnswer = () => {
let answer = view.find('.answer').val();
let test = problemData.code.replace('__', answer) + '; problem();';
return eval(test);
};

let checkAnswerClick = () => {
if (checkAnswer()) {
let correctFlash = learnjs.buildCorrectFlash(problemNumber);
learnjs.flashElement(resultFlash, correctFlash);
} else {
learnjs.flashElement(resultFlash, 'Incorrect!');
}
return false;
};

view.find('.check-btn').click(checkAnswerClick);
view.find('.title').text('Problem #' + problemNumber);
learnjs.applyObject(problemData, view);
return view;
};

learnjs.showView = (hash) => {
let routes = {
'#problem': learnjs.problemView,
'#': learnjs.landingView,
'': learnjs.landingView,
};
let hashParts = hash.split('-');
let viewFn = routes[hashParts[0]];
if (viewFn) {
learnjs.triggerEvent('removingView', []);
$('.view-container').empty().append(viewFn(hashParts[1]));
}
};

learnjs.flashElement = (elem, content) => {
elem.fadeOut('fast', () => {
elem.html(content);
elem.fadeIn();
});
};

learnjs.template = (name) => {
return $('.templates .' + name).clone();
};

learnjs.buildCorrectFlash = (problemNumber) => {
let correctFlash = learnjs.template('correct-flash');
let link = correctFlash.find('a');
if (problemNumber < learnjs.problems.length) {
link.attr('href', '#problem-' + (problemNumber + 1));
} else {
link.attr('href', '');
link.text('You\'re Finished!');
}
return correctFlash;
};

learnjs.landingView = function() {
return learnjs.template('landing-view');
};

learnjs.triggerEvent = (name, args) => {
$('.view-container>*').trigger(name, args);
};

109 changes: 97 additions & 12 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset='utf-8'>
<title>Learn JS!</title>
<link href='//fonts.googleapis.com/css?family=Raleway:400,300,600' rel='stylesheet' type='text/css'>
Expand All @@ -9,17 +9,102 @@
<script src='https://code.jquery.com/jquery-2.1.4.min.js'></script>
<script src='/vendor.js'></script>
<script src='/app.js'></script>
<style type="text/css" media="all">
body { margin-top: 30px; }
<style type='text/css' media='all'>
body {
margin-top: 30px;
}

.text-1g {
font-size: x-large;
}

.templates {
display: none;
}

.inline-list {
margin-bottom: 0px; /* Skleton reset */
}

.inline-list li {
display: inline;
margin: 0 20px 0 0;
}

.fixed-top {
position: fixed;
top: 0px;
z-index: 1024;
}

.no-select {
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
}

.hover-links a {
text-decoration: none;
}

.hover-links a:hover {
text-decoration: underline;
}

.nav-container {
padding-left: 40px;
background: #666;
}

.nav-container a {
color: white;
}
</style>
</head>
<body>
<div class='container'>
<h1>It works!</h1>
<div>
<span>You're ready to start!</span>
<span>Skeleton 2, jQuery 2, and AWS Libraries are included.</span>
</div>
</head>
<body>
<div class='markup'>
<div class='nav-container no-select fixed-top u-full-width'>
<ul class='inline-list hover-links nav-list six columns'>
<li><a class='text-1g' href='#'>LearnJS</a></li>
<li><a href='#problem-1'>Start</a></li>
</ul>
</div>
</body>
<div class='view-container container'></div>
<div class='templates'>
<div class='landing-view'>
<div class='row'>
<div class='one-half column'>
<h3>Learn JavaScript, one puzzle at a time.</h3>
<a href='#problem-1' class='button button-primary'>Start Now!</a>
</div>
<div class='one-half column'>
<img src='/images/HeroImage.jpg'/>
</div>
</div>
</div>
<div class='problem-view'>
<h3 class='title'></h3>
<p data-name='description'></p>
<pre><code data-name='code'></code></pre>
<form>
<textarea class='u-full-width answer'></textarea>
<div>
<button class='button-primary check-btn'>Check Answer</button>
<p class='result'></p>
</div>
</form>
</div>
<div class='correct-flash'>
<span>Correct!</span> <a>Next Problem</a>
</div>
<li class='skip-btn'>
<a>Skip This Problem</a>
</li>
</div>
</div>
<script type='text/javascript'>
$(window).ready(learnjs.appOnReady);
</script>
</body>
</html>
97 changes: 97 additions & 0 deletions public/tests/app_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
describe('LearnJS', function() {
it('can show a problem view', function() {
learnjs.showView('#problem-1');
expect($('.view-container .problem-view').length).toEqual(1);
});
it('shows the landing page view when there is no hash', function() {
learnjs.showView('');
expect($('.view-container .landing-view').length).toEqual(1);
});
it('passes the hash view parameter to the view function', () => {
spyOn(learnjs, 'problemView');
learnjs.showView('#problem-42');
expect(learnjs.problemView).toHaveBeenCalledWith('42');
});
describe('problem view', () => {
it('has a title that includes the problem number', () => {
let view = learnjs.problemView('1');
expect(view.find('.title').text().trim()).toEqual('Problem #1');
});
it('show the description that binds data', () => {
let view = learnjs.problemView('1');
expect(view.find('[data-name="description"]').text().trim()).
toEqual(learnjs.problems[0].description);
});
it('show the problem code that binds data', () => {
let view = learnjs.problemView('1');
expect(view.find('[data-name="code"]').text().trim()).
toEqual(learnjs.problems[0].code);
});
});
it('invokes the router when loaded', () => {
spyOn(learnjs, 'showView');
learnjs.appOnReady();
expect(learnjs.showView).toHaveBeenCalledWith(window.location.hash);
});
it('subscribes to the hash change event', () => {
learnjs.appOnReady();
spyOn(learnjs, 'showView');
$(window).trigger('hashchange');
expect(learnjs.showView).toHaveBeenCalledWith(window.location.hash);
});
describe('answer section', () => {
let view = undefined;
beforeEach(() => {
view = learnjs.problemView('1');
});
it('can check a correct answer by hitting a button', () => {
view.find('.answer').val('true');
view.find('.check-btn').click();
expect(view.find('.result').text().trim()).
toEqual('Correct! Next Problem');
});
it('rejects an incorrect answer', () => {
view.find('.answer').val('false');
view.find('.check-btn').click();
expect(view.find('.result').text().trim()).toEqual('Incorrect!');
});
});
describe('navigation', () => {
it('have a next problem', () => {
let correctFlash = learnjs.buildCorrectFlash(1);
expect(correctFlash.text().trim()).toEqual('Correct! Next Problem');
expect(correctFlash.find('a').attr('href')).toEqual('#problem-2');
});
it('finished', () => {
let correctFlash = learnjs.buildCorrectFlash(learnjs.problems.length);
expect(correctFlash.text().trim()).toEqual('Correct! You\'re Finished!');
expect(correctFlash.find('a').attr('href')).toEqual('');
});
});
describe('application shell', () => {
it('landing page link on toolbar', () => {
let link = $('.nav-container > ul a:contains("LearnJS")');
expect(link.attr('href')).toEqual('#');
});
it('start link on toolbar', () => {
let link = $('.nav-container > ul a:contains("Start")');
expect(link.attr('href')).toEqual('#problem-1');
});
it('not have skip link on toolbar in landing page', () => {
learnjs.showView('');
let link = $('.nav-container > ul a:contains("Skip This Problem")');
expect(link.length).toEqual(0);
});
it('have skip link on toolbar in problem view', () => {
learnjs.problemView('1');
let link = $('.nav-container > ul a:contains("Skip This Problem")');
expect(link.attr('href')).toEqual('#problem-2');
});
it('not have skip link on toolbar in final problem view', () => {
learnjs.problemView(learnjs.problems.length);
let link = $('.nav-container > ul a:contains("Skip This Problem")');
expect(link.length).toEqual(0);
});
});
});