From 5f5f9e526e08171a84c01d51e0f4d29820f74a2e Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Fri, 10 Jun 2016 14:01:13 -0700 Subject: [PATCH] Fix next challenge loading logic --- client/sagas/execute-challenge-saga.js | 4 +- .../challenges/components/classic/Output.jsx | 19 ++--- .../components/classic/Tool-Panel.jsx | 15 ++-- .../challenges/redux/next-challenge-saga.js | 70 +++++++++++++--- common/app/routes/challenges/redux/reducer.js | 7 +- common/app/routes/challenges/utils.js | 80 ++++++++++++++++--- .../html5-and-css.json | 8 +- 7 files changed, 154 insertions(+), 49 deletions(-) diff --git a/client/sagas/execute-challenge-saga.js b/client/sagas/execute-challenge-saga.js index 867556ba46..de914dccbe 100644 --- a/client/sagas/execute-challenge-saga.js +++ b/client/sagas/execute-challenge-saga.js @@ -53,8 +53,8 @@ function cacheScript({ src } = {}) { const frameRunner$ = cacheScript({ src: '/js/frame-runner.js' }); -const htmlCatch = '\n'; -const jsCatch = '\n;/* */'; +const htmlCatch = '\n'; +const jsCatch = '\n;/*fcc*/'; export default function executeChallengeSaga(action$, getState) { return action$ diff --git a/common/app/routes/challenges/components/classic/Output.jsx b/common/app/routes/challenges/components/classic/Output.jsx index 1f66b996d9..888f8aef9d 100644 --- a/common/app/routes/challenges/components/classic/Output.jsx +++ b/common/app/routes/challenges/components/classic/Output.jsx @@ -1,16 +1,8 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; import PureComponent from 'react-pure-render/component'; import NoSSR from 'react-no-ssr'; import Codemirror from 'react-codemirror'; -const defaultOutput = `/** - * Your output will go here. - * Any console.log() -type - * statements will appear in - * your browser\'s DevTools - * JavaScript console. - */`; - const defaultOptions = { lineNumbers: false, mode: 'javascript', @@ -21,11 +13,9 @@ const defaultOptions = { export default class extends PureComponent { static displayName = 'Output'; - - static defaultProps = { - output: defaultOutput + static propTypes = { + output: PropTypes.string }; - render() { const { output } = this.props; return ( @@ -33,7 +23,8 @@ export default class extends PureComponent { + value={ output } + /> ); diff --git a/common/app/routes/challenges/components/classic/Tool-Panel.jsx b/common/app/routes/challenges/components/classic/Tool-Panel.jsx index 7e59c6cbec..16f9e95f22 100644 --- a/common/app/routes/challenges/components/classic/Tool-Panel.jsx +++ b/common/app/routes/challenges/components/classic/Tool-Panel.jsx @@ -22,29 +22,34 @@ export class ToolPanel extends PureComponent { block={ true } bsStyle='primary' className='btn-big' - onClick={ executeChallenge }> + onClick={ executeChallenge } + > Run tests (ctrl + enter)
+ justified={ true } + > diff --git a/common/app/routes/challenges/redux/next-challenge-saga.js b/common/app/routes/challenges/redux/next-challenge-saga.js index 6403045292..06a15c11d8 100644 --- a/common/app/routes/challenges/redux/next-challenge-saga.js +++ b/common/app/routes/challenges/redux/next-challenge-saga.js @@ -1,24 +1,68 @@ import { Observable } from 'rx'; import { push } from 'react-router-redux'; import { moveToNextChallenge } from './types'; -import { getNextChallenge } from '../utils'; import { resetUi, updateCurrentChallenge } from './actions'; -// import { createErrorObservable, makeToast } from '../../../redux/actions'; +import { createErrorObservable, makeToast } from '../../../redux/actions'; +import { + getNextChallenge, + getFirstChallengeOfNextBlock, + getFirstChallengeOfNextSuperBlock +} from '../utils'; +import { getRandomVerb } from '../../../utils/get-words'; export default function nextChallengeSaga(actions$, getState) { return actions$ .filter(({ type }) => type === moveToNextChallenge) .flatMap(() => { - const state = getState(); - const nextChallenge = getNextChallenge( - state.challengesApp.challenge, - state.entities, - state.challengesApp.superBlocks - ); - return Observable.of( - updateCurrentChallenge(nextChallenge), - resetUi(), - push(`/challenges/${nextChallenge.block}/${nextChallenge.dashedName}`) - ); + let nextChallenge; + // let message = ''; + // let isNewBlock = false; + // let isNewSuperBlock = false; + try { + const state = getState(); + const { challenge, superBlocks } = state.challengesApp; + const { entities } = state; + nextChallenge = getNextChallenge(challenge, entities); + // block completed. + if (!nextChallenge) { + // isNewBlock = true; + nextChallenge = getFirstChallengeOfNextBlock(challenge, entities); + } + // superBlock completed + if (!nextChallenge) { + // isNewSuperBlock = true; + nextChallenge = getFirstChallengeOfNextSuperBlock( + challenge, + entities, + superBlocks + ); + } + /* this requires user data not available yet + if (isNewSuperBlock || isNewBlock) { + const getName = isNewSuperBlock ? + getCurrentSuperBlockName : + getCurrentBlockName; + const blockType = isNewSuperBlock ? 'SuperBlock' : 'Block'; + message = + `You've competed the ${getName(challenge, entities)} ${blockType}!`; + } + message += ' Your next challenge has arrived.'; + const toast = { + // title: isNewSuperBlock || isNewBlock ? getRandomVerb() : null, + message + }; + */ + return Observable.of( + updateCurrentChallenge(nextChallenge), + resetUi(), + makeToast({ + title: getRandomVerb(), + message: 'Your next challenge has arrived.' + }), + push(`/challenges/${nextChallenge.block}/${nextChallenge.dashedName}`) + ); + } catch (err) { + return createErrorObservable(err); + } }); } diff --git a/common/app/routes/challenges/redux/reducer.js b/common/app/routes/challenges/redux/reducer.js index 9fce3e35b8..f224ec9801 100644 --- a/common/app/routes/challenges/redux/reducer.js +++ b/common/app/routes/challenges/redux/reducer.js @@ -15,7 +15,12 @@ const initialUiState = { currentIndex: 0, previousIndex: -1, isActionCompleted: false, - isSubmitting: true + isSubmitting: true, + output: `/** + * Any console.log() + * statements will appear in + * here console. + */` }; const initialState = { id: '', diff --git a/common/app/routes/challenges/utils.js b/common/app/routes/challenges/utils.js index 74da4aebce..dff8cfffdd 100644 --- a/common/app/routes/challenges/utils.js +++ b/common/app/routes/challenges/utils.js @@ -91,20 +91,80 @@ export function getFirstChallenge( ]; } -export function getNextChallenge( - current, - entites, - superBlocks -) { +export function getNextChallenge(current, entites) { const { challenge: challengeMap, block: blockMap } = entites; // find current challenge // find current block // find next challenge in block const currentChallenge = challengeMap[current]; - if (currentChallenge) { - const block = blockMap[currentChallenge.block]; - const index = block.challenges.indexOf(currentChallenge.dashedName); - return challengeMap[block.challenges[index + 1]]; + if (!currentChallenge) { + return null; } - return getFirstChallenge(entites, superBlocks); + const block = blockMap[currentChallenge.block]; + const index = block.challenges.indexOf(currentChallenge.dashedName); + return challengeMap[block.challenges[index + 1]]; +} + +export function getFirstChallengeOfNextBlock(current, entites) { + const { + challenge: challengeMap, + block: blockMap, + superBlock: SuperBlockMap + } = entites; + const currentChallenge = challengeMap[current]; + if (!currentChallenge) { + return null; + } + const block = blockMap[currentChallenge.block]; + if (!block) { + return null; + } + const superBlock = SuperBlockMap[block.superBlock]; + const index = superBlock.blocks.indexOf(block.dashedName); + const newBlock = superBlock.blocks[ index + 1 ]; + if (!newBlock) { + return null; + } + return challengeMap[newBlock.challenges[0]]; +} + +export function getFirstChallengeOfNextSuperBlock( + current, + entites, + superBlocks +) { + const { + challenge: challengeMap, + block: blockMap, + superBlock: SuperBlockMap + } = entites; + const currentChallenge = challengeMap[current]; + if (!currentChallenge) { + return null; + } + const block = blockMap[currentChallenge.block]; + if (!block) { + return null; + } + const superBlock = SuperBlockMap[block.superBlock]; + const index = superBlocks.indexOf(superBlock.dashedName); + const newSuperBlock = SuperBlockMap[superBlocks[ index + 1]]; + if (!newSuperBlock) { + return null; + } + const newBlock = blockMap[newSuperBlock.blocks[0]]; + return challengeMap[newBlock.challenges[0]]; +} + +export function getCurrentBlockName(current, entities) { + const { challenge: challengeMap } = entities; + const challenge = challengeMap[current]; + return challenge.block; +} + +export function getCurrentSuperBlockName(current, entities) { + const { challenge: challengeMap, block: blockMap } = entities; + const challenge = challengeMap[current]; + const block = blockMap[challenge.block]; + return block.superBlock; } diff --git a/seed/challenges/01-front-end-development-certification/html5-and-css.json b/seed/challenges/01-front-end-development-certification/html5-and-css.json index 30a9bc002e..37159cfcce 100644 --- a/seed/challenges/01-front-end-development-certification/html5-and-css.json +++ b/seed/challenges/01-front-end-development-certification/html5-and-css.json @@ -265,7 +265,7 @@ "assert($(\"h1\").length > 0, 'message: Make your h1 element visible on your page by uncommenting it.');", "assert($(\"h2\").length > 0, 'message: Make your h2 element visible on your page by uncommenting it.');", "assert($(\"p\").length > 0, 'message: Make your p element visible on your page by uncommenting it.');", - "assert(!/-->/gi.test(code.replace(/ */gi.test(code.replace(/ */g).length > 1, 'message: Be sure to close each of your comments with -->.');", + "assert(code.match(/[^fc]-->/g).length > 1, 'message: Be sure to close each of your comments with -->.');", "assert((code.match(/<([a-z0-9]){1,2}>/g)[0]===\"

\" && code.match(/<([a-z0-9]){1,2}>/g)[1]===\"

\" && code.match(/<([a-z0-9]){1,2}>/g)[2]===\"

\") , 'message: Do not change the order of the h1 h2 or p in the code.');" ], "type": "waypoint", @@ -1071,8 +1071,8 @@ "tests": [ "assert($(\"h2\").css(\"font-family\").match(/^\"?lobster/i), 'message: Your h2 element should use the font Lobster.');", "assert($(\"h2\").css(\"font-family\").match(/lobster.*,.*monospace/i), 'message: Your h2 element should degrade to the font Monospace when Lobster is not available.');", - "assert(new RegExp(\"\", \"gi\").test(code), 'message: Be sure to close your comment by adding -->.');" + "assert(new RegExp(\"\", \"gi\").test(code), 'message: Be sure to close your comment by adding -->.');" ], "type": "waypoint", "titleEs": "Especifica cómo deben degradarse los tipos de letra",