Skip to content

Commit

Permalink
Merge pull request #2325 from bryanjenningz/add-exercise-card-to-dojo…
Browse files Browse the repository at this point in the history
…-exercises-page

Add exercise card to DOJO exercises page
  • Loading branch information
bryanjenningz authored Sep 19, 2022
2 parents ee91a31 + d5888a0 commit d60042b
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 10 deletions.
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}>
<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">
{mockExercisePreviews.map((exercisePreview, i) => (
Expand All @@ -68,7 +202,7 @@ const Exercises: React.FC<QueryDataProps<GetAppQuery>> = ({ queryData }) => {
))}
</div>
</div>
</Layout>
</>
)
}

Expand Down

1 comment on commit d60042b

@vercel
Copy link

@vercel vercel bot commented on d60042b Sep 19, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.