diff --git a/common/app/routes/challenges/Show.jsx b/common/app/routes/challenges/Show.jsx index 1e03366527..1d274a9fe3 100644 --- a/common/app/routes/challenges/Show.jsx +++ b/common/app/routes/challenges/Show.jsx @@ -10,6 +10,7 @@ import Classic from './views/classic'; import Step from './views/step'; import Project from './views/project'; import BackEnd from './views/backend'; +import Quiz from './views/quiz'; import { challengeMetaSelector } from './redux'; import { @@ -27,7 +28,8 @@ const views = { classic: Classic, project: Project, simple: Project, - step: Step + step: Step, + quiz: Quiz }; const mapDispatchToProps = { diff --git a/common/app/routes/challenges/index.js b/common/app/routes/challenges/index.js index fff3d571d1..a0eea2dd3e 100644 --- a/common/app/routes/challenges/index.js +++ b/common/app/routes/challenges/index.js @@ -3,13 +3,15 @@ import { panesMap as backendPanesMap } from './views/backend'; import { panesMap as classicPanesMap } from './views/classic'; import { panesMap as stepPanesMap } from './views/step'; import { panesMap as projectPanesMap } from './views/project'; +import { panesMap as quizPanesMap } from './views/quiz'; export function createPanesMap() { return { ...backendPanesMap, ...classicPanesMap, ...stepPanesMap, - ...projectPanesMap + ...projectPanesMap, + ...quizPanesMap }; } diff --git a/common/app/routes/challenges/redux/completion-epic.js b/common/app/routes/challenges/redux/completion-epic.js index ab7265aab1..8432a1a8b3 100644 --- a/common/app/routes/challenges/redux/completion-epic.js +++ b/common/app/routes/challenges/redux/completion-epic.js @@ -138,6 +138,7 @@ const submitters = { backend: submitBackendChallenge, step: submitSimpleChallenge, video: submitSimpleChallenge, + quiz: submitSimpleChallenge, 'project.frontEnd': submitProject, 'project.backEnd': submitProject, 'project.simple': submitSimpleChallenge diff --git a/common/app/routes/challenges/redux/index.js b/common/app/routes/challenges/redux/index.js index 61cef59898..5feb5d5ca1 100644 --- a/common/app/routes/challenges/redux/index.js +++ b/common/app/routes/challenges/redux/index.js @@ -27,6 +27,7 @@ import { bonfire, html, js } from '../../../utils/challengeTypes'; import blockNameify from '../../../utils/blockNameify'; import { createPoly, setContent } from '../../../../utils/polyvinyl'; import createStepReducer, { epics as stepEpics } from '../views/step/redux'; +import createQuizReducer from '../views/quiz/redux'; import createProjectReducer from '../views/project/redux'; // this is not great but is ok until we move to a different form type @@ -361,6 +362,7 @@ export default function createReducers() { return [ reducer, ...createStepReducer(), - ...createProjectReducer() + ...createProjectReducer(), + ...createQuizReducer() ]; } diff --git a/common/app/routes/challenges/utils.js b/common/app/routes/challenges/utils.js index 39140c3429..45ead3e449 100644 --- a/common/app/routes/challenges/utils.js +++ b/common/app/routes/challenges/utils.js @@ -14,6 +14,7 @@ export const viewTypes = { // formally hikes [ challengeTypes.video ]: 'video', [ challengeTypes.step ]: 'step', + [ challengeTypes.quiz ]: 'quiz', backend: 'backend' }; @@ -34,6 +35,7 @@ export const submitTypes = { // formally hikes [ challengeTypes.video ]: 'video', [ challengeTypes.step ]: 'step', + [ challengeTypes.quiz ]: 'quiz', backend: 'backend' }; diff --git a/common/app/routes/challenges/views/index.less b/common/app/routes/challenges/views/index.less index 839c5aa630..6a485e0b71 100644 --- a/common/app/routes/challenges/views/index.less +++ b/common/app/routes/challenges/views/index.less @@ -1,2 +1,3 @@ &{ @import "./classic/classic.less"; } &{ @import "./step/step.less"; } +&{ @import "./quiz/quiz.less"; } diff --git a/common/app/routes/challenges/views/project/Show.jsx b/common/app/routes/challenges/views/project/Show.jsx index 79fc4c204d..e3d470120f 100644 --- a/common/app/routes/challenges/views/project/Show.jsx +++ b/common/app/routes/challenges/views/project/Show.jsx @@ -2,12 +2,11 @@ import React from 'react'; import Main from './Project.jsx'; import { types } from '../../redux'; +import Panes from '../../../../Panes'; import _Map from '../../../../Map'; import ChildContainer from '../../../../Child-Container.jsx'; -import Panes from '../../../../Panes'; const propTypes = {}; - export const panesMap = { [types.toggleMap]: 'Map', [types.toggleMain]: 'Main' diff --git a/common/app/routes/challenges/views/quiz/Choice.jsx b/common/app/routes/challenges/views/quiz/Choice.jsx new file mode 100644 index 0000000000..0ba6e9c7e9 --- /dev/null +++ b/common/app/routes/challenges/views/quiz/Choice.jsx @@ -0,0 +1,82 @@ +import React, { PropTypes, PureComponent } from 'react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import classnames from 'classnames'; +import { createSelector } from 'reselect'; + +import { + selectChoice, + incrementCorrect +} from './redux'; + +const mapStateToProps = createSelector( + () => ({}) +); + +function mapDispatchToProps(dispatch) { + return () => bindActionCreators({ + selectChoice, + incrementCorrect + }, dispatch); +} + +const propTypes = { + choice: PropTypes.string, + choiceIndex: PropTypes.number, + incrementCorrect: PropTypes.func, + isChoiceSelected: PropTypes.bool, + isCorrectChoice: PropTypes.bool, + selectChoice: PropTypes.func, + selected: PropTypes.bool +}; + +export class Choice extends PureComponent { + + constructor(props) { + super(props); + this.selectChoice = this.selectChoice.bind(this); + } + + selectChoice() { + if (this.props.isChoiceSelected) { + return; + } + if (this.props.isCorrectChoice) { + this.props.incrementCorrect(); + } + this.props.selectChoice(this.props.choiceIndex); + } + + render() { + const choiceClass = classnames({ + choice: true, + selected: this.props.selected, + correct: this.props.isCorrectChoice, + reveal: this.props.isChoiceSelected + }); + + return ( +
+ You got {this.props.correct} out of + {this.props.description.length} correct! +
+ + {isQuizPassed === false ? ( ++ You will need to get all the questions + correct in order to mark this quiz as completed. +
+ +const results = [1.32, 2.43, 3.9]\n .map(Math.floor);\nconsole.log(results);
",
+ "choices": [
+ "1.32 2.43 3.9
",
+ "['1.32', '2.43', '3.9']
",
+ "[1, 2, 3]
",
+ "'1 2 3'
"
+ ],
+ "answer": 2,
+ "explanation": "The map function takes a callback function as it's first parameter and applies that function against every value inside the array. In this example, our callback function is the Math.floor
function which will truncate the decimal points of all the numbers and convert them to integers."
+ },
+ {
+ "subtitle": "Custom Map Functions",
+ "question": "What will the following code print out?\nconst results = ['a', 'b', 'c']\n .map(a => [a.toUpperCase()]);\nconsole.log(results);
",
+ "choices": [
+ "[['A'], ['B'], ['C']]
",
+ "['A', 'B', 'C']
",
+ "['a', 'b', 'c]
",
+ "'ABC'
"
+ ],
+ "answer": 0,
+ "explanation": "The map function will return a new array with each element equal to the old element ran through a callback function. Our callback function takes our original element, changes it to a upper case, and then wraps it in an array; thus, leaving us with [['A', 'B', 'C']]
"
+ },
+ {
+ "subtitle": "Maps on Maps",
+ "question": "What will the following code print out?\nconst results = [[4, 1], [2, 0], [3, 3]]\n .map(a => \n a.map(b => b % 2)[0] + a.map(b => b - 1)[1]\n )\nconsole.log(results);
",
+ "choices": [
+ "[[0, 1], [0, 0], [1, 1]]
",
+ "[[0, 0], [0, -1], [1, 2]]
",
+ "[1, 1, 2]
",
+ "[0, -1, 3]
"
+ ],
+ "answer": 3,
+ "explanation": "This answer can be explained by first looking at the example of what happens with the first element in our array, [4, 1]
. Our first map callback will run a mod 2 map function over [4, 1]
leaving us with a new array of [0, 1]
. The second map call which is inside our callback will subtract 1 from every element, leaving us with [3, 0]
. Last, we take element at index 0, 0
, and add it to element of index 1 from our second map function, 0
, leaving us with 0; thus, after the first iteration of the top level map function, we are left with an array that looks like so: [1, [2, 0], [3, 3]]
. We simply keep doing that logic for the other elements until we finish: [1, -1, [3, 3]]
, and [1, -1, 3]
"
+ },
+ {
+ "subtitle": "Words Containing 'a'",
+ "question": "What will the following code print out?\nconst results = ['apple', 'dog', 'cat']\n .map((a, i) => \n a.indexOf('a') !== -1 ? i : null)\nconsole.log(results);
",
+ "choices": [
+ "[0, -1, 1]
",
+ "[0, null, 2]
",
+ "[null, null, null]
",
+ "[-1, null, 2]
"
+ ],
+ "answer": 1,
+ "explanation": "This map example will return an array where each elements of the new array is either the original array index when the element contains the character 'a'; otherwise, an element of null for any words that do not have the character 'a'."
+ },
+ {
+ "subtitle": "Accessing the Original Array Elements",
+ "question": "What will the following code print out?\nconst results = [1, 2, 3]\n .map((a, _, o) => a + o[0])\nconsole.log(results);
",
+ "choices": [
+ "[1, 2, 3]
",
+ "[0, 0, 0]
",
+ "[3, 2, 1]
",
+ "[2, 3, 4]
"
+ ],
+ "answer": 3,
+ "explanation": "This map example will add the value of the first element in the original array to all the other elements in the array."
+ },
+ {
+ "subtitle": "More Map Hacking",
+ "question": "What will the following code print out?\nconst results = [8, 5, 3]\n .map((a, i, o) => o[o.length - i - i])\nconsole.log(results);
",
+ "choices": [
+ "[3, 5, 8]
",
+ "[5, 3, 8]
",
+ "[8, 5, 3]
",
+ "[3, 8, 5]
"
+ ],
+ "answer": 0,
+ "explanation": "This map example will reverse the array. The third argument to the map callback function is the original array; therefore, we can use the current index in the map function, and work our way backwards from the end of the array using the o.length."
+ },
+ {
+ "subtitle": "Custom Scoping",
+ "question": "What will the following code print out?\nconst results = ['a', 'b', 'c']\n .map(function(a) { return this[a]; }, {a: 9, b: 3, c: 1})\nconsole.log(results);
",
+ "choices": [
+ "['a', 'b', 'c']
",
+ "[9, 3, 1]
",
+ "[3, 9, 1]
",
+ "[{a: 9}, {b: 3}, {c: 1}]
"
+ ],
+ "answer": 1,
+ "explanation": "This map example will reverse the array. The third argument to the map callback function is the original array; therefore, we can use the current index in the map function, and work our way backwards from the end of the array using the o.length."
+ },
+ {
+ "subtitle": "Reversing in Map, Just Because",
+ "question": "What will the following code print out?\nconst results = [1, 2, 3, 4, 5]\n .map((a, _, o) => o.reverse() && a)\nconsole.log(results);
",
+ "choices": [
+ "[5, 4, 3, 2, 1]
",
+ "[5, 2, 3, 5, 1]
",
+ "[1, 2, 3, 4, 5]
",
+ "[1, 4, 3, 2, 5]
"
+ ],
+ "answer": 3,
+ "explanation": "This map example will reverse the array. The third argument to the map callback function is the original array; therefore, we can use the current index in the map function, and work our way backwards from the end of the array using the o.length."
+ }
+ ],
+ "tests": [],
+ "challengeType": 8
+ }
+ ]
+}