diff --git a/public/app.js b/public/app.js
index e69de29bb..6014f0eab 100644
--- a/public/app.js
+++ b/public/app.js
@@ -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);
+};
+
diff --git a/public/index.html b/public/index.html
index 036a914c8..1195c0fec 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,6 +1,6 @@
-
+
Learn JS!
@@ -9,17 +9,102 @@
-
-
-
-
-
It works!
-
- You're ready to start!
- Skeleton 2, jQuery 2, and AWS Libraries are included.
-
+
+
+
+
+
diff --git a/public/tests/app_spec.js b/public/tests/app_spec.js
index e69de29bb..2b86ce86c 100644
--- a/public/tests/app_spec.js
+++ b/public/tests/app_spec.js
@@ -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);
+ });
+ });
+});
+