feat(quiz): initial quiz view which can be used for multiple choice (#15743)
quizes
This commit is contained in:
committed by
Quincy Larson
parent
93148665c6
commit
9f875e1d11
@ -10,6 +10,7 @@ import Classic from './views/classic';
|
|||||||
import Step from './views/step';
|
import Step from './views/step';
|
||||||
import Project from './views/project';
|
import Project from './views/project';
|
||||||
import BackEnd from './views/backend';
|
import BackEnd from './views/backend';
|
||||||
|
import Quiz from './views/quiz';
|
||||||
|
|
||||||
import { challengeMetaSelector } from './redux';
|
import { challengeMetaSelector } from './redux';
|
||||||
import {
|
import {
|
||||||
@ -27,7 +28,8 @@ const views = {
|
|||||||
classic: Classic,
|
classic: Classic,
|
||||||
project: Project,
|
project: Project,
|
||||||
simple: Project,
|
simple: Project,
|
||||||
step: Step
|
step: Step,
|
||||||
|
quiz: Quiz
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
@ -3,13 +3,15 @@ import { panesMap as backendPanesMap } from './views/backend';
|
|||||||
import { panesMap as classicPanesMap } from './views/classic';
|
import { panesMap as classicPanesMap } from './views/classic';
|
||||||
import { panesMap as stepPanesMap } from './views/step';
|
import { panesMap as stepPanesMap } from './views/step';
|
||||||
import { panesMap as projectPanesMap } from './views/project';
|
import { panesMap as projectPanesMap } from './views/project';
|
||||||
|
import { panesMap as quizPanesMap } from './views/quiz';
|
||||||
|
|
||||||
export function createPanesMap() {
|
export function createPanesMap() {
|
||||||
return {
|
return {
|
||||||
...backendPanesMap,
|
...backendPanesMap,
|
||||||
...classicPanesMap,
|
...classicPanesMap,
|
||||||
...stepPanesMap,
|
...stepPanesMap,
|
||||||
...projectPanesMap
|
...projectPanesMap,
|
||||||
|
...quizPanesMap
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,7 @@ const submitters = {
|
|||||||
backend: submitBackendChallenge,
|
backend: submitBackendChallenge,
|
||||||
step: submitSimpleChallenge,
|
step: submitSimpleChallenge,
|
||||||
video: submitSimpleChallenge,
|
video: submitSimpleChallenge,
|
||||||
|
quiz: submitSimpleChallenge,
|
||||||
'project.frontEnd': submitProject,
|
'project.frontEnd': submitProject,
|
||||||
'project.backEnd': submitProject,
|
'project.backEnd': submitProject,
|
||||||
'project.simple': submitSimpleChallenge
|
'project.simple': submitSimpleChallenge
|
||||||
|
@ -27,6 +27,7 @@ import { bonfire, html, js } from '../../../utils/challengeTypes';
|
|||||||
import blockNameify from '../../../utils/blockNameify';
|
import blockNameify from '../../../utils/blockNameify';
|
||||||
import { createPoly, setContent } from '../../../../utils/polyvinyl';
|
import { createPoly, setContent } from '../../../../utils/polyvinyl';
|
||||||
import createStepReducer, { epics as stepEpics } from '../views/step/redux';
|
import createStepReducer, { epics as stepEpics } from '../views/step/redux';
|
||||||
|
import createQuizReducer from '../views/quiz/redux';
|
||||||
import createProjectReducer from '../views/project/redux';
|
import createProjectReducer from '../views/project/redux';
|
||||||
|
|
||||||
// this is not great but is ok until we move to a different form type
|
// this is not great but is ok until we move to a different form type
|
||||||
@ -361,6 +362,7 @@ export default function createReducers() {
|
|||||||
return [
|
return [
|
||||||
reducer,
|
reducer,
|
||||||
...createStepReducer(),
|
...createStepReducer(),
|
||||||
...createProjectReducer()
|
...createProjectReducer(),
|
||||||
|
...createQuizReducer()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ export const viewTypes = {
|
|||||||
// formally hikes
|
// formally hikes
|
||||||
[ challengeTypes.video ]: 'video',
|
[ challengeTypes.video ]: 'video',
|
||||||
[ challengeTypes.step ]: 'step',
|
[ challengeTypes.step ]: 'step',
|
||||||
|
[ challengeTypes.quiz ]: 'quiz',
|
||||||
backend: 'backend'
|
backend: 'backend'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ export const submitTypes = {
|
|||||||
// formally hikes
|
// formally hikes
|
||||||
[ challengeTypes.video ]: 'video',
|
[ challengeTypes.video ]: 'video',
|
||||||
[ challengeTypes.step ]: 'step',
|
[ challengeTypes.step ]: 'step',
|
||||||
|
[ challengeTypes.quiz ]: 'quiz',
|
||||||
backend: 'backend'
|
backend: 'backend'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
&{ @import "./classic/classic.less"; }
|
&{ @import "./classic/classic.less"; }
|
||||||
&{ @import "./step/step.less"; }
|
&{ @import "./step/step.less"; }
|
||||||
|
&{ @import "./quiz/quiz.less"; }
|
||||||
|
@ -2,12 +2,11 @@ import React from 'react';
|
|||||||
|
|
||||||
import Main from './Project.jsx';
|
import Main from './Project.jsx';
|
||||||
import { types } from '../../redux';
|
import { types } from '../../redux';
|
||||||
|
import Panes from '../../../../Panes';
|
||||||
import _Map from '../../../../Map';
|
import _Map from '../../../../Map';
|
||||||
import ChildContainer from '../../../../Child-Container.jsx';
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
import Panes from '../../../../Panes';
|
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {};
|
||||||
|
|
||||||
export const panesMap = {
|
export const panesMap = {
|
||||||
[types.toggleMap]: 'Map',
|
[types.toggleMap]: 'Map',
|
||||||
[types.toggleMain]: 'Main'
|
[types.toggleMain]: 'Main'
|
||||||
|
82
common/app/routes/challenges/views/quiz/Choice.jsx
Normal file
82
common/app/routes/challenges/views/quiz/Choice.jsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
className={choiceClass}
|
||||||
|
onClick={this.selectChoice}
|
||||||
|
>
|
||||||
|
<div className='radio'>
|
||||||
|
<div className='inside' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className='text'
|
||||||
|
dangerouslySetInnerHTML={{__html: this.props.choice}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Choice.displayName = 'Choice';
|
||||||
|
Choice.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Choice);
|
222
common/app/routes/challenges/views/quiz/Quiz.jsx
Normal file
222
common/app/routes/challenges/views/quiz/Quiz.jsx
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import React, { PropTypes, PureComponent } from 'react';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { Col, Row } from 'react-bootstrap';
|
||||||
|
import Choice from './Choice.jsx';
|
||||||
|
|
||||||
|
import {
|
||||||
|
currentIndexSelector,
|
||||||
|
selectedChoiceSelector,
|
||||||
|
nextQuestion,
|
||||||
|
selectChoice,
|
||||||
|
correctSelector,
|
||||||
|
incrementCorrect,
|
||||||
|
resetQuiz,
|
||||||
|
resetChoice
|
||||||
|
} from './redux';
|
||||||
|
|
||||||
|
import { submitChallenge, challengeMetaSelector } from '../../redux';
|
||||||
|
import { challengeSelector } from '../../../../redux';
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(
|
||||||
|
challengeSelector,
|
||||||
|
challengeMetaSelector,
|
||||||
|
currentIndexSelector,
|
||||||
|
selectedChoiceSelector,
|
||||||
|
correctSelector,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
description = [],
|
||||||
|
title
|
||||||
|
},
|
||||||
|
meta,
|
||||||
|
currentIndex,
|
||||||
|
selectedChoice,
|
||||||
|
correct
|
||||||
|
) => ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
meta,
|
||||||
|
currentIndex,
|
||||||
|
selectedChoice,
|
||||||
|
correct
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return () => bindActionCreators({
|
||||||
|
nextQuestion,
|
||||||
|
selectChoice,
|
||||||
|
incrementCorrect,
|
||||||
|
resetQuiz,
|
||||||
|
resetChoice,
|
||||||
|
submitChallenge
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
correct: PropTypes.number,
|
||||||
|
currentIndex: PropTypes.number,
|
||||||
|
description: PropTypes.string,
|
||||||
|
meta: PropTypes.object,
|
||||||
|
nextQuestion: PropTypes.fun,
|
||||||
|
resetChoice: PropTypes.fun,
|
||||||
|
resetQuiz: PropTypes.fun,
|
||||||
|
selectedChoice: PropTypes.number,
|
||||||
|
submitChallenge: PropTypes.fun
|
||||||
|
};
|
||||||
|
|
||||||
|
export class QuizChallenge extends PureComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.nextQuestion = this.nextQuestion.bind(this);
|
||||||
|
this.submitChallenge = this.submitChallenge.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextQuestion() {
|
||||||
|
this.props.resetChoice();
|
||||||
|
this.props.nextQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
submitChallenge() {
|
||||||
|
this.props.resetQuiz();
|
||||||
|
this.props.submitChallenge();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitle() {
|
||||||
|
return (
|
||||||
|
<Row className='quizTitle'>
|
||||||
|
<Col md={12}>
|
||||||
|
<h4>{this.props.meta.title}</h4>
|
||||||
|
<hr/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResults() {
|
||||||
|
const isQuizPassed = this.props.correct === this.props.description.length;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderTitle()}
|
||||||
|
<Row className='quizResults'>
|
||||||
|
<Col md={12}>
|
||||||
|
<h2>Quiz Results:</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You got {this.props.correct} out of
|
||||||
|
{this.props.description.length} correct!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{isQuizPassed === false ? (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You will need to get all the questions
|
||||||
|
correct in order to mark this quiz as completed.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='btn btn-lg btn-primary'
|
||||||
|
onClick={this.props.resetQuiz}
|
||||||
|
> Try Again
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className='btn btn-lg btn-primary'
|
||||||
|
onClick={this.submitChallenge}
|
||||||
|
> Finish Quiz
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderQuiz() {
|
||||||
|
const currentIndex = this.props.currentIndex;
|
||||||
|
const question = this.props.description[currentIndex];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderTitle()}
|
||||||
|
<Row>
|
||||||
|
<Col md={6}>
|
||||||
|
<h2 className='textCenter'>
|
||||||
|
Question {currentIndex + 1} of {this.props.description.length}:
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
{question.subtitle}:
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p dangerouslySetInnerHTML={{__html: question.question}} />
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col md={6}>
|
||||||
|
<h2 className='textCenter'>Choices</h2>
|
||||||
|
|
||||||
|
{question.choices.map((choice, i) => (
|
||||||
|
<Choice
|
||||||
|
choice={choice}
|
||||||
|
choiceIndex={i}
|
||||||
|
isChoiceSelected={this.props.selectedChoice !== null}
|
||||||
|
isCorrectChoice={question.answer === i}
|
||||||
|
key={choice}
|
||||||
|
selected={i === this.props.selectedChoice}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{this.props.selectedChoice !== null &&
|
||||||
|
<Row className='quizResults'>
|
||||||
|
<Col md={6} mdPush={3}>
|
||||||
|
<div className='messageDiv'>
|
||||||
|
{this.props.selectedChoice === question.answer
|
||||||
|
? <h2 className='correctAnswer'>
|
||||||
|
Correct, great work!
|
||||||
|
</h2>
|
||||||
|
: <h2 className='wrongAnswer'>
|
||||||
|
Sorry, that is not correct!
|
||||||
|
</h2>}
|
||||||
|
</div>
|
||||||
|
{this.props.selectedChoice !== question.answer &&
|
||||||
|
<div className='explanation'>
|
||||||
|
<h2>Explanation:</h2>
|
||||||
|
<p dangerouslySetInnerHTML={{__html: question.explanation}} />
|
||||||
|
</div>}
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col md={12}>
|
||||||
|
<button
|
||||||
|
className='btn btn-lg btn-primary'
|
||||||
|
onClick={this.nextQuestion}
|
||||||
|
>
|
||||||
|
{currentIndex + 1 === this.props.description.length ?
|
||||||
|
'View Results' : 'Next Question'}
|
||||||
|
</button>
|
||||||
|
</Col>
|
||||||
|
</Row>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='quiz'>{
|
||||||
|
this.props.currentIndex >= this.props.description.length ?
|
||||||
|
this.renderResults() : this.renderQuiz()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QuizChallenge.displayName = 'QuizChallenge';
|
||||||
|
QuizChallenge.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(QuizChallenge);
|
33
common/app/routes/challenges/views/quiz/Show.jsx
Normal file
33
common/app/routes/challenges/views/quiz/Show.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Main from './Quiz.jsx';
|
||||||
|
import { types } from '../../redux';
|
||||||
|
import Panes from '../../../../Panes';
|
||||||
|
import _Map from '../../../../Map';
|
||||||
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
|
||||||
|
const propTypes = {};
|
||||||
|
export const panesMap = {
|
||||||
|
[types.toggleMap]: 'Map',
|
||||||
|
[types.toggleMain]: 'Main'
|
||||||
|
};
|
||||||
|
|
||||||
|
const nameToComponent = {
|
||||||
|
Map: {
|
||||||
|
Component: _Map
|
||||||
|
},
|
||||||
|
Main: {
|
||||||
|
Component: Main
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ShowQuiz() {
|
||||||
|
return (
|
||||||
|
<ChildContainer isFullWidth={ true }>
|
||||||
|
<Panes nameToComponent={ nameToComponent }/>
|
||||||
|
</ChildContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowQuiz.displayName = 'ShowQuiz';
|
||||||
|
ShowQuiz.propTypes = propTypes;
|
1
common/app/routes/challenges/views/quiz/index.js
Normal file
1
common/app/routes/challenges/views/quiz/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default, panesMap } from './Show.jsx';
|
1
common/app/routes/challenges/views/quiz/ns.json
Normal file
1
common/app/routes/challenges/views/quiz/ns.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"quiz"
|
93
common/app/routes/challenges/views/quiz/quiz.less
Normal file
93
common/app/routes/challenges/views/quiz/quiz.less
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// should match ./ns.json value and filename
|
||||||
|
@ns: quiz;
|
||||||
|
|
||||||
|
.quiz {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
.quizTitle {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textCenter {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explanation p {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quizResults {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrongAnswer {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.correctAnswer {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice.selected {
|
||||||
|
.radio {
|
||||||
|
.inside {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice:not(.reveal):hover > .radio {
|
||||||
|
.inside {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 100%;
|
||||||
|
border: 4px solid #333;
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
.inside {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
left: 2px;
|
||||||
|
top: 2px;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice:not(.reveal):hover {
|
||||||
|
background-color: rgba(0, 100, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice:not(.reveal) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice.reveal {
|
||||||
|
background-color: rgba(255, 65, 77, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice.reveal.correct {
|
||||||
|
background-color: rgba(0, 100, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
79
common/app/routes/challenges/views/quiz/redux/index.js
Normal file
79
common/app/routes/challenges/views/quiz/redux/index.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { createTypes } from 'redux-create-types';
|
||||||
|
import { createAction, handleActions } from 'redux-actions';
|
||||||
|
import noop from 'lodash/noop';
|
||||||
|
|
||||||
|
import ns from '../ns.json';
|
||||||
|
|
||||||
|
export const types = createTypes([
|
||||||
|
'nextQuestion',
|
||||||
|
'selectChoice',
|
||||||
|
'incrementCorrect',
|
||||||
|
'resetQuiz',
|
||||||
|
'resetChoice'
|
||||||
|
], ns);
|
||||||
|
|
||||||
|
export const nextQuestion = createAction(
|
||||||
|
types.nextQuestion,
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectChoice = createAction(
|
||||||
|
types.selectChoice,
|
||||||
|
(selectedChoice) => ({ selectedChoice })
|
||||||
|
);
|
||||||
|
|
||||||
|
export const incrementCorrect = createAction(
|
||||||
|
types.incrementCorrect,
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
export const resetQuiz = createAction(
|
||||||
|
types.resetQuiz,
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
export const resetChoice = createAction(
|
||||||
|
types.resetChoice,
|
||||||
|
noop
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
currentIndex: 0,
|
||||||
|
selectedChoice: null,
|
||||||
|
correct: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNS = state => state[ns];
|
||||||
|
export const currentIndexSelector = state => getNS(state).currentIndex;
|
||||||
|
export const selectedChoiceSelector = state => getNS(state).selectedChoice;
|
||||||
|
export const correctSelector = state => getNS(state).correct;
|
||||||
|
|
||||||
|
export default function createReducers() {
|
||||||
|
const reducer = handleActions({
|
||||||
|
[types.nextQuestion]: state => ({
|
||||||
|
...state,
|
||||||
|
currentIndex: state.currentIndex + 1
|
||||||
|
}),
|
||||||
|
[types.selectChoice]: (state, {payload}) => ({
|
||||||
|
...state,
|
||||||
|
selectedChoice: payload.selectedChoice
|
||||||
|
}),
|
||||||
|
[types.incrementCorrect]: state => ({
|
||||||
|
...state,
|
||||||
|
correct: state.correct + 1
|
||||||
|
}),
|
||||||
|
[types.resetQuiz]: state => ({
|
||||||
|
...state,
|
||||||
|
currentIndex: 0,
|
||||||
|
correct: 0,
|
||||||
|
selectedChoice: null
|
||||||
|
}),
|
||||||
|
[types.resetChoice]: state => ({
|
||||||
|
...state,
|
||||||
|
selectedChoice: null
|
||||||
|
})
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
reducer.toString = () => ns;
|
||||||
|
return [ reducer ];
|
||||||
|
}
|
@ -9,3 +9,4 @@ export const backEndProject = 4;
|
|||||||
export const bonfire = 5;
|
export const bonfire = 5;
|
||||||
export const video = 6;
|
export const video = 6;
|
||||||
export const step = 7;
|
export const step = 7;
|
||||||
|
export const quiz = 8;
|
||||||
|
@ -81,7 +81,13 @@
|
|||||||
""
|
""
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"challengeSeed": [],
|
"challengeSeed": [
|
||||||
|
"function sym(args) {",
|
||||||
|
" return args;",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"sym([1, 2, 3], [5, 2, 1, 4]);"
|
||||||
|
],
|
||||||
"tests": [],
|
"tests": [],
|
||||||
"type": "Waypoint",
|
"type": "Waypoint",
|
||||||
"challengeType": 7,
|
"challengeType": 7,
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"name": "JavaScript Multiple Choice Questions",
|
||||||
|
"order": 8,
|
||||||
|
"time": "",
|
||||||
|
"helpRoom": "HelpJavaScript",
|
||||||
|
"challenges": [
|
||||||
|
{
|
||||||
|
"id": "59874fc749228906236a3275",
|
||||||
|
"title": "Array.prototype.map",
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"subtitle": "Flooring an Array",
|
||||||
|
"question": "What will the following code print out?\n<pre><code class='language-javascript'>const results = [1.32, 2.43, 3.9]\n .map(Math.floor);\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>1.32 2.43 3.9</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>['1.32', '2.43', '3.9']</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[1, 2, 3]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>'1 2 3'</code></pre>"
|
||||||
|
],
|
||||||
|
"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 <code>Math.floor</code> 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?\n<pre><code class='language-javascript'>const results = ['a', 'b', 'c']\n .map(a => [a.toUpperCase()]);\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>[['A'], ['B'], ['C']]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>['A', 'B', 'C']</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>['a', 'b', 'c]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>'ABC'</code></pre>"
|
||||||
|
],
|
||||||
|
"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 <code>[['A', 'B', 'C']]</code>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Maps on Maps",
|
||||||
|
"question": "What will the following code print out?\n<pre><code class='language-javascript'>const 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);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>[[0, 1], [0, 0], [1, 1]]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[[0, 0], [0, -1], [1, 2]]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[1, 1, 2]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[0, -1, 3]</code></pre>"
|
||||||
|
],
|
||||||
|
"answer": 3,
|
||||||
|
"explanation": "This answer can be explained by first looking at the example of what happens with the first element in our array, <code>[4, 1]</code>. Our first map callback will run a mod 2 map function over <code>[4, 1]</code> leaving us with a new array of <code>[0, 1]</code>. The second map call which is inside our callback will subtract 1 from every element, leaving us with <code>[3, 0]</code>. Last, we take element at index 0, <code>0</code>, and add it to element of index 1 from our second map function, <code>0</code>, 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: <code>[1, [2, 0], [3, 3]]</code>. We simply keep doing that logic for the other elements until we finish: <code>[1, -1, [3, 3]]</code>, and <code>[1, -1, 3]</code>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Words Containing 'a'",
|
||||||
|
"question": "What will the following code print out?\n<pre><code class='language-javascript'>const results = ['apple', 'dog', 'cat']\n .map((a, i) => \n a.indexOf('a') !== -1 ? i : null)\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>[0, -1, 1]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[0, null, 2]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[null, null, null]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[-1, null, 2]</code></pre>"
|
||||||
|
],
|
||||||
|
"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?\n<pre><code class='language-javascript'>const results = [1, 2, 3]\n .map((a, _, o) => a + o[0])\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>[1, 2, 3]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[0, 0, 0]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[3, 2, 1]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[2, 3, 4]</code></pre>"
|
||||||
|
],
|
||||||
|
"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?\n<pre><code class='language-javascript'>const results = [8, 5, 3]\n .map((a, i, o) => o[o.length - i - i])\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>[3, 5, 8]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[5, 3, 8]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[8, 5, 3]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[3, 8, 5]</code></pre>"
|
||||||
|
],
|
||||||
|
"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?\n<pre><code class='language-javascript'>const results = ['a', 'b', 'c']\n .map(function(a) { return this[a]; }, {a: 9, b: 3, c: 1})\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>['a', 'b', 'c']</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[9, 3, 1]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[3, 9, 1]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[{a: 9}, {b: 3}, {c: 1}]</code></pre>"
|
||||||
|
],
|
||||||
|
"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?\n<pre><code class='language-javascript'>const results = [1, 2, 3, 4, 5]\n .map((a, _, o) => o.reverse() && a)\nconsole.log(results);</code></pre>",
|
||||||
|
"choices": [
|
||||||
|
"<pre><code class='language-javascript'>[5, 4, 3, 2, 1]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[5, 2, 3, 5, 1]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[1, 2, 3, 4, 5]</code></pre>",
|
||||||
|
"<pre><code class='language-javascript'>[1, 4, 3, 2, 5]</code></pre>"
|
||||||
|
],
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user