Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark exercise answered based on user answers #2351

Merged
merged 6 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions __dummy__/getExercisesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const getExercisesData: GetExercisesQuery = {
alerts: [],
exercises: [
{
id: 1,
module: {
name: 'Numbers',
lesson: {
Expand All @@ -68,6 +69,7 @@ const getExercisesData: GetExercisesQuery = {
explanation: 'You can reassign variables that were created with "let".'
},
{
id: 2,
module: {
name: 'Numbers',
lesson: {
Expand All @@ -79,6 +81,7 @@ const getExercisesData: GetExercisesQuery = {
explanation: '`a += 2` is a shorter way to write `a = a + 2`'
},
{
id: 3,
module: {
name: 'Numbers',
lesson: {
Expand Down
14 changes: 14 additions & 0 deletions components/ExerciseCard/ExerciseCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('ExerciseCard component', () => {
it('Should render an exercise card', async () => {
const setAnswerShown = jest.fn()
const setMessage = jest.fn()
const submitUserAnswer = jest.fn()

const { getByRole, queryByText } = render(
<ExerciseCard
Expand All @@ -23,6 +24,7 @@ describe('ExerciseCard component', () => {
setAnswerShown={setAnswerShown}
message={Message.EMPTY}
setMessage={setMessage}
submitUserAnswer={submitUserAnswer}
/>
)

Expand All @@ -36,11 +38,14 @@ describe('ExerciseCard component', () => {
expect(setAnswerShown).toBeCalledTimes(0)
expect(setMessage).toBeCalledWith(Message.ERROR)
expect(setMessage).toBeCalledTimes(1)
expect(submitUserAnswer).toBeCalledWith('')
expect(submitUserAnswer).toBeCalledTimes(1)
})

it('Should render an error message', () => {
const setAnswerShown = jest.fn()
const setMessage = jest.fn()
const submitUserAnswer = jest.fn()

const { getByRole, queryByText, getByLabelText } = render(
<ExerciseCard
Expand All @@ -51,6 +56,7 @@ describe('ExerciseCard component', () => {
setAnswerShown={setAnswerShown}
message={Message.ERROR}
setMessage={setMessage}
submitUserAnswer={submitUserAnswer}
/>
)

Expand All @@ -71,11 +77,14 @@ describe('ExerciseCard component', () => {
expect(setAnswerShown).toBeCalledTimes(1)
expect(setMessage).toBeCalledWith(Message.SUCCESS)
expect(setMessage).toBeCalledTimes(1)
expect(submitUserAnswer).toBeCalledWith('15')
expect(submitUserAnswer).toBeCalledTimes(1)
})

it('Should render a success message', () => {
const setAnswerShown = jest.fn()
const setMessage = jest.fn()
const submitUserAnswer = jest.fn()

const { getByRole, queryByText } = render(
<ExerciseCard
Expand All @@ -86,6 +95,7 @@ describe('ExerciseCard component', () => {
setAnswerShown={setAnswerShown}
message={Message.SUCCESS}
setMessage={setMessage}
submitUserAnswer={submitUserAnswer}
/>
)

Expand All @@ -100,11 +110,13 @@ describe('ExerciseCard component', () => {
expect(setAnswerShown).toBeCalledWith(false)
expect(setAnswerShown).toBeCalledTimes(1)
expect(setMessage).toBeCalledTimes(0)
expect(submitUserAnswer).toBeCalledTimes(0)
})

it('Should hide the answer', () => {
const setAnswerShown = jest.fn()
const setMessage = jest.fn()
const submitUserAnswer = jest.fn()

const { queryByText, getByRole } = render(
<ExerciseCard
Expand All @@ -115,6 +127,7 @@ describe('ExerciseCard component', () => {
setAnswerShown={setAnswerShown}
message={Message.SUCCESS}
setMessage={setMessage}
submitUserAnswer={submitUserAnswer}
/>
)

Expand All @@ -129,5 +142,6 @@ describe('ExerciseCard component', () => {
expect(setAnswerShown).toBeCalledWith(true)
expect(setAnswerShown).toBeCalledTimes(1)
expect(setMessage).toBeCalledTimes(0)
expect(submitUserAnswer).toBeCalledTimes(0)
})
})
5 changes: 4 additions & 1 deletion components/ExerciseCard/ExerciseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ExerciseCardProps = {
setAnswerShown: (answerShown: boolean) => void
message: Message
setMessage: (message: Message) => void
submitUserAnswer: (userAnswer: string) => void
}

export enum Message {
Expand All @@ -26,7 +27,8 @@ const ExerciseCard = ({
answerShown,
setAnswerShown,
message,
setMessage
setMessage,
submitUserAnswer
}: ExerciseCardProps) => {
const [studentAnswer, setStudentAnswer] = useState('')

Expand Down Expand Up @@ -65,6 +67,7 @@ const ExerciseCard = ({
} else {
setMessage(Message.ERROR)
}
submitUserAnswer(studentAnswer.trim())
}}
>
SUBMIT
Expand Down
2 changes: 2 additions & 0 deletions graphql/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,7 @@ export type GetExercisesQuery = {
}>
exercises: Array<{
__typename?: 'Exercise'
id: number
description: string
answer: string
explanation?: string | null
Expand Down Expand Up @@ -3531,6 +3532,7 @@ export const GetExercisesDocument = gql`
urlCaption
}
exercises {
id
module {
name
lesson {
Expand Down
1 change: 1 addition & 0 deletions graphql/queries/getExercises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const GET_EXERCISES = gql`
urlCaption
}
exercises {
id
module {
name
lesson {
Expand Down
55 changes: 31 additions & 24 deletions pages/exercises/[lessonSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,21 @@ import Error, { StatusCode } from '../../components/Error'
import LoadingSpinner from '../../components/LoadingSpinner'
import AlertsDisplay from '../../components/AlertsDisplay'
import NavCard from '../../components/NavCard'
import ExercisePreviewCard, {
ExercisePreviewCardProps
} from '../../components/ExercisePreviewCard'
import ExercisePreviewCard from '../../components/ExercisePreviewCard'
import { NewButton } from '../../components/theme/Button'
import ExerciseCard, { Message } from '../../components/ExerciseCard'
import { ArrowLeftIcon } from '@primer/octicons-react'
import GET_EXERCISES from '../../graphql/queries/getExercises'
import styles from '../../scss/exercises.module.scss'

const exampleProblem = `const a = 5
a = a + 10
// what is a?`

const mockExercisePreviews: ExercisePreviewCardProps[] = [
{ moduleName: 'Variables', state: 'ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'NOT ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'NOT ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'NOT ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'NOT ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'NOT ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'NOT ANSWERED', problem: exampleProblem },
{ moduleName: 'Variables', state: 'ANSWERED', problem: exampleProblem }
]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the mock exercise previews, we're now using the backend exercises for the previews.

const Exercises: React.FC<QueryDataProps<GetExercisesQuery>> = ({
queryData
}) => {
const { lessons, alerts, exercises } = queryData
const router = useRouter()
const [exerciseIndex, setExerciseIndex] = useState(-1)
const [userAnswers, setUserAnswers] = useState<Record<number, string>>({})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We store the user's answers on the frontend for now. They go away when you refresh the page. This is good for testing. In the next pull request, we'll get the user's answers from the backend.


if (!router.isReady) return <LoadingSpinner />

const slug = router.query.lessonSlug as string
Expand All @@ -60,6 +45,7 @@ const Exercises: React.FC<QueryDataProps<GetExercisesQuery>> = ({
const currentExercises = exercises
.filter(exercise => exercise?.module.lesson.slug === slug)
.map(exercise => ({
id: exercise.id,
challengeName: exercise.module.name,
problem: exercise.description,
answer: exercise.answer,
Expand All @@ -78,12 +64,21 @@ const Exercises: React.FC<QueryDataProps<GetExercisesQuery>> = ({
lessonTitle={currentLesson.title}
hasPrevious={exerciseIndex > 0}
hasNext={exerciseIndex < currentExercises.length - 1}
submitUserAnswer={(userAnswer: string) =>
setUserAnswers({ ...userAnswers, [exercise.id]: userAnswer })
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we add the user's answer to our userAnswers object which stores all the user's answers by exercise ID.

}
/>
) : (
<ExerciseList
tabs={tabs}
setExerciseIndex={setExerciseIndex}
lessonTitle={currentLesson.title}
exercises={exercises.map(exercise => ({
problem: exercise.description,
answer: exercise.answer,
moduleName: exercise.module.name,
userAnswer: userAnswers[exercise.id] ?? null
}))}
/>
)}
{alerts && <AlertsDisplay alerts={alerts} />}
Expand All @@ -103,14 +98,16 @@ type ExerciseProps = {
lessonTitle: string
hasPrevious: boolean
hasNext: boolean
submitUserAnswer: (userAnswer: string) => void
}

const Exercise = ({
exercise,
setExerciseIndex,
lessonTitle,
hasPrevious,
hasNext
hasNext,
submitUserAnswer
}: ExerciseProps) => {
const [answerShown, setAnswerShown] = useState(false)
const [message, setMessage] = useState(Message.EMPTY)
Expand All @@ -133,6 +130,7 @@ const Exercise = ({
setAnswerShown={setAnswerShown}
message={message}
setMessage={setMessage}
submitUserAnswer={submitUserAnswer}
/>
<div className="d-flex justify-content-between mt-4">
{hasPrevious ? (
Expand Down Expand Up @@ -170,12 +168,19 @@ type ExerciseListProps = {
tabs: { text: string; url: string }[]
setExerciseIndex: React.Dispatch<React.SetStateAction<number>>
lessonTitle: string
exercises: {
moduleName: string
problem: string
answer: string
userAnswer: string | null
}[]
}

const ExerciseList = ({
tabs,
setExerciseIndex,
lessonTitle
lessonTitle,
exercises
}: ExerciseListProps) => {
return (
<>
Expand All @@ -199,14 +204,16 @@ const ExerciseList = ({
</div>
</div>
<div className={styles.exerciseList__container}>
{mockExercisePreviews.map((exercisePreview, i) => (
{exercises.map((exercise, i) => (
<ExercisePreviewCard
key={i}
moduleName={exercisePreview.moduleName}
state={exercisePreview.state}
problem={exercisePreview.problem}
moduleName={exercise.moduleName}
state={exercise.userAnswer === null ? 'NOT ANSWERED' : 'ANSWERED'}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now there are only 2 states, but I think there should be a third state in the future called "ANSWERED INCORRECTLY" or something similar. Maybe we can make "ANSWERED INCORRECTLY" red and then "NOT ANSWERED" can be gray or some neutral color.

problem={exercise.problem}
/>
))}
<div />
<div />
</div>
</>
)
Expand Down
3 changes: 3 additions & 0 deletions stories/components/ExerciseCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const Basic = () => {
setAnswerShown={setAnswerShown}
message={message}
setMessage={setMessage}
submitUserAnswer={userAnswer => {
console.log(`User answer submitted: ${userAnswer}`)
}}
/>
)
}