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. -
+ + +
+ - +
+
+
+
+
+

Learn JavaScript, one puzzle at a time.

+ Start Now! +
+
+ +
+
+
+
+

+

+
+
+ +
+ +

+
+
+
+
+ Correct! Next Problem +
+
  • + Skip This Problem +
  • +
    +
    + + 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); + }); + }); +}); +