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

Add exercise card to DOJO exercises page #2325

Merged
merged 8 commits into from
Sep 19, 2022
2 changes: 1 addition & 1 deletion __tests__/__snapshots__/storyshots.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5219,7 +5219,7 @@ Array [

exports[`Storyshots Components/ExerciseCard Basic 1`] = `
<section
className="card p-4 border-0 shadow"
className="card p-5 border-0 shadow"
>
<div
className="fw-bold mb-2"
Expand Down
52 changes: 51 additions & 1 deletion __tests__/pages/exercises/[lessonSlug].test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { render, waitFor, screen } from '@testing-library/react'
import { render, waitFor, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import Exercises from '../../../pages/exercises/[lessonSlug]'
import { useRouter } from 'next/router'
Expand Down Expand Up @@ -48,6 +48,56 @@ describe('Exercises page', () => {
screen.getByRole('link', { name: 'LESSONS' })
})

test('Renders exercise card with the skip, previous, and exit buttons', async () => {
const mocks = [
{
request: { query: GET_APP },
result: {
data: {
session,
lessons: dummyLessonData,
alerts: dummyAlertData
}
}
}
]

const { getByRole, queryByRole } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<Exercises />
</MockedProvider>
)

await waitFor(() =>
getByRole('heading', { name: /Foundations of JavaScript/i })
)

const solveExercisesButton = getByRole('button', {
name: 'SOLVE EXERCISES'
})
fireEvent.click(solveExercisesButton)

// Previous button is not in the document on the first exercise.
expect(queryByRole('button', { name: 'PREVIOUS' })).not.toBeInTheDocument()

const skipButton = getByRole('button', { name: 'SKIP' })
fireEvent.click(skipButton)
expect(queryByRole('button', { name: 'PREVIOUS' })).toBeInTheDocument()

fireEvent.click(skipButton)
// Skip button should not be in the document because we're on the last exercise now.
expect(queryByRole('button', { name: 'SKIP' })).not.toBeInTheDocument()

const previousButton = getByRole('button', { name: 'PREVIOUS' })
fireEvent.click(previousButton)
expect(queryByRole('button', { name: 'SKIP' })).toBeInTheDocument()

const exitButton = getByRole('button', { name: 'Exit' })
fireEvent.click(exitButton)
expect(queryByRole('button', { name: 'PREVIOUS' })).not.toBeInTheDocument()
expect(queryByRole('button', { name: 'SKIP' })).not.toBeInTheDocument()
})

test('Should not render lessons nav card tab if lesson docUrl is null', async () => {
const mocks = [
{
Expand Down
2 changes: 1 addition & 1 deletion components/ExerciseCard/ExerciseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const ExerciseCard = ({
const [answerShown, setAnswerShown] = useState(false)

return (
<section className="card p-4 border-0 shadow">
<section className="card p-5 border-0 shadow">
<div className="fw-bold mb-2">Problem</div>
<div className="d-flex mb-2">
<pre className="w-50 bg-light py-3 px-4 mb-0 me-3">{problem}</pre>
Expand Down
148 changes: 141 additions & 7 deletions pages/exercises/[lessonSlug].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRouter } from 'next/router'
import React from 'react'
import React, { useState } from 'react'
import Layout from '../../components/Layout'
import withQueryLoader, {
QueryDataProps
Expand All @@ -13,20 +13,54 @@ import NavCard from '../../components/NavCard'
import ExercisePreviewCard, {
ExercisePreviewCardProps
} from '../../components/ExercisePreviewCard'
import { NewButton } from '../../components/theme/Button'
import ExerciseCard, { ExerciseCardProps } from '../../components/ExerciseCard'
import { ArrowLeftIcon } from '@primer/octicons-react'

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

const exampleProblem2 = `let a = 5
a = a + 10
// what is a?`

const exampleProblem3 = `let 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: 'ANSWERED', problem: exampleProblem }
]

const mockExercises: ExerciseCardProps[] = [
{
challengeName: 'Variable mutation',
problem: exampleProblem,
answer: 'An error is thrown',
explanation: 'You cannot reassign variables that were created with "const".'
},
{
challengeName: 'Variable mutation 2',
problem: exampleProblem2,
answer: '15',
explanation: 'You can reassign variables that were created with "let".'
},
{
challengeName: 'Variable mutation 3',
problem: exampleProblem3,
answer: `'510'`,
explanation:
'If you add a string and number together, the number gets converted to a string and then they get concatenated.'
}
]

const Exercises: React.FC<QueryDataProps<GetAppQuery>> = ({ queryData }) => {
const { lessons, alerts } = queryData
const router = useRouter()
const [exerciseIndex, setExerciseIndex] = useState(-1)
if (!router.isReady) return <LoadingSpinner />

const slug = router.query.lessonSlug as string
Expand All @@ -45,14 +79,114 @@ const Exercises: React.FC<QueryDataProps<GetAppQuery>> = ({ queryData }) => {
{ text: 'exercises', url: `/exercises/${currentLesson.slug}` }
]

const exercise = mockExercises[exerciseIndex]

return (
<Layout title={currentLesson.title}>
bryanjenningz marked this conversation as resolved.
Show resolved Hide resolved
<NavCard
tabSelected={tabs.findIndex(tab => tab.text === 'exercises')}
tabs={tabs}
/>
<h1 className="my-4">{currentLesson.title}</h1>
{exercise ? (
<Exercise
exercise={exercise}
setExerciseIndex={setExerciseIndex}
lessonTitle={currentLesson.title}
showPreviousButton={exerciseIndex > 0}
showSkipButton={exerciseIndex < mockExercises.length - 1}
/>
) : (
<ExerciseList
tabs={tabs}
setExerciseIndex={setExerciseIndex}
lessonTitle={currentLesson.title}
/>
)}
{alerts && <AlertsDisplay alerts={alerts} />}
</Layout>
)
}

type ExerciseProps = {
exercise: ExerciseCardProps
setExerciseIndex: React.Dispatch<React.SetStateAction<number>>
lessonTitle: string
showPreviousButton: boolean
showSkipButton: boolean
}

const Exercise = ({
exercise,
setExerciseIndex,
lessonTitle,
showPreviousButton,
showSkipButton
}: ExerciseProps) => {
return (
<div className="w-75 mx-auto">
<button
className="btn ps-0 d-flex align-items-center"
onClick={() => setExerciseIndex(-1)}
>
<ArrowLeftIcon size="medium" aria-label="Exit" />
</button>

<h1 className="mb-4 fs-2">{lessonTitle}</h1>
<ExerciseCard
challengeName={exercise.challengeName}
problem={exercise.problem}
answer={exercise.answer}
explanation={exercise.explanation}
/>
<div className="d-flex justify-content-between mt-4">
{showPreviousButton ? (
<button
onClick={() => setExerciseIndex(i => i - 1)}
className="btn btn-outline-primary fw-bold px-4 py-2"
style={{ fontFamily: 'PT Mono', fontSize: 14 }}
>
PREVIOUS
</button>
) : (
<div />
)}
{showSkipButton ? (
<button
onClick={() => setExerciseIndex(i => i + 1)}
className="btn btn-outline-primary fw-bold px-4 py-2"
style={{ fontFamily: 'PT Mono', fontSize: 14 }}
>
SKIP
</button>
) : (
<div />
)}
</div>
</div>
)
}

type ExerciseListProps = {
tabs: { text: string; url: string }[]
setExerciseIndex: React.Dispatch<React.SetStateAction<number>>
lessonTitle: string
}

const ExerciseList = ({
tabs,
setExerciseIndex,
lessonTitle
}: ExerciseListProps) => {
return (
<>
<div className="mb-4">
<NavCard
tabSelected={tabs.findIndex(tab => tab.text === 'exercises')}
tabs={tabs}
/>
</div>
<div className="d-flex justify-content-between align-items-center">
<h1 className="my-5 fs-2">{lessonTitle}</h1>
<NewButton onClick={() => setExerciseIndex(0)}>
SOLVE EXERCISES
</NewButton>
</div>
<div className="container">
<div className="row">
bryanjenningz marked this conversation as resolved.
Show resolved Hide resolved
{mockExercisePreviews.map((exercisePreview, i) => (
Expand All @@ -68,7 +202,7 @@ const Exercises: React.FC<QueryDataProps<GetAppQuery>> = ({ queryData }) => {
))}
</div>
</div>
</Layout>
</>
)
}

Expand Down