Skip to content

Commit e3411d0

Browse files
committed
feat(quiz): allow revealing correct answers on success
1 parent 6b83ce3 commit e3411d0

File tree

5 files changed

+349
-12
lines changed

5 files changed

+349
-12
lines changed

src/quiz-question/answer.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ export const Answer = <AnswerT extends number | string>({
113113
const getRadioWrapperCls = () => {
114114
const cls = [...radioWrapperDefaultClasses];
115115

116-
if (checked && validation?.state === "correct")
116+
if (validation?.state === "correct")
117117
cls.push("border-l-background-success");
118-
if (checked && validation?.state === "incorrect")
118+
if (validation?.state === "incorrect")
119119
cls.push("border-l-background-danger");
120120

121121
return cls.join(" ");

src/quiz-question/quiz-question.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const QuizQuestion = <AnswerT extends number | string>({
7575
feedback={checked && validation && feedback}
7676
checked={checked}
7777
disabled={disabled}
78-
validation={checked ? validation : undefined}
78+
validation={validation}
7979
/>
8080
);
8181
})}

src/quiz/quiz.stories.tsx

+224
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,111 @@ const QuizWithValidationAndAnswerFeedback = () => {
224224
);
225225
};
226226

227+
const QuizWithCorrectAnswersShownOnSuccess = () => {
228+
const initialQuestions: Question<number>[] = [
229+
{
230+
question: "Lorem ipsum dolor sit amet",
231+
answers: [
232+
{
233+
label: "Option 1",
234+
value: 1,
235+
feedback: (
236+
<PrismFormatted
237+
text={`<p>Quaerat in autem sapiente illum. Vel mollitia omnis qui dolorem <code>um</code> esse eos maiores possimus. Est laborum quam aliquam qui sunt. Ut ea et qui provident voluptatibus. Eius quam odit sint cumque sint. Corporis quia et dicta.</p>`}
238+
getCodeBlockAriaLabel={(codeName) => `${codeName} code example`}
239+
/>
240+
),
241+
},
242+
{
243+
label: "Option 2",
244+
value: 2,
245+
feedback:
246+
"Recusandae necessitatibus consequatur voluptatem sapiente.",
247+
},
248+
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
249+
],
250+
correctAnswer: 1,
251+
},
252+
{
253+
question: "Consectetur adipiscing elit",
254+
answers: [
255+
{
256+
label: "Option 1",
257+
value: 1,
258+
feedback: (
259+
<PrismFormatted
260+
text={`<p>Quaerat in autem sapiente illum. Vel mollitia omnis qui dolorem <code>um</code> esse eos maiores possimus. Est laborum quam aliquam qui sunt. Ut ea et qui provident voluptatibus. Eius quam odit sint cumque sint. Corporis quia et dicta.</p>`}
261+
getCodeBlockAriaLabel={(codeName) => `${codeName} code example`}
262+
/>
263+
),
264+
},
265+
{
266+
label: "Option 2",
267+
value: 2,
268+
feedback:
269+
"Recusandae necessitatibus consequatur voluptatem sapiente.",
270+
},
271+
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
272+
],
273+
correctAnswer: 2,
274+
},
275+
{
276+
question: "Fugit itaque delectus voluptatem alias aliquid",
277+
answers: [
278+
{
279+
label: "Option 1",
280+
value: 1,
281+
feedback: (
282+
<PrismFormatted
283+
text={`<p>Quaerat in autem sapiente illum. Vel mollitia omnis qui dolorem <code>um</code> esse eos maiores possimus. Est laborum quam aliquam qui sunt. Ut ea et qui provident voluptatibus. Eius quam odit sint cumque sint. Corporis quia et dicta.</p>`}
284+
getCodeBlockAriaLabel={(codeName) => `${codeName} code example`}
285+
/>
286+
),
287+
},
288+
{
289+
label: "Option 2",
290+
value: 2,
291+
feedback:
292+
"Recusandae necessitatibus consequatur voluptatem sapiente.",
293+
},
294+
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
295+
],
296+
correctAnswer: 3,
297+
},
298+
];
299+
300+
const { questions, validateAnswers, correctAnswerCount } = useQuiz({
301+
initialQuestions,
302+
validationMessages: {
303+
correct: "Correct.",
304+
incorrect: "Incorrect.",
305+
},
306+
passingGrade: 50,
307+
showCorrectAnswersOnSuccess: true,
308+
});
309+
const [disabled, setDisabled] = useState(false);
310+
311+
const handleSubmit = () => {
312+
validateAnswers();
313+
setDisabled(true);
314+
};
315+
316+
return (
317+
<div>
318+
<div aria-live="polite">
319+
{!!correctAnswerCount && (
320+
<p className="text-foreground-primary">
321+
Correct answers: {correctAnswerCount}
322+
</p>
323+
)}
324+
</div>
325+
<Quiz questions={questions} disabled={disabled} />
326+
<Spacer size="m" />
327+
<Button onClick={handleSubmit}>Submit</Button>
328+
</div>
329+
);
330+
};
331+
227332
export const Default: Story = {
228333
render: QuizDefault,
229334
args: {},
@@ -471,4 +576,123 @@ const App = () => {
471576
},
472577
};
473578

579+
export const WithCorrectAnswersShownOnSuccess: Story = {
580+
render: QuizWithCorrectAnswersShownOnSuccess,
581+
args: {},
582+
parameters: {
583+
docs: {
584+
source: {
585+
code: `
586+
import { Quiz, useQuiz, Button, Spacer } from '@freecodecamp/ui';
587+
588+
const initialQuestions = [
589+
{
590+
question: "Lorem ipsum dolor sit amet",
591+
answers: [
592+
{
593+
label: "Option 1",
594+
value: 1,
595+
feedback: (
596+
<PrismFormatted
597+
text={\`<p>Quaerat in autem sapiente illum. Vel mollitia omnis qui dolorem <code>um</code> esse eos maiores possimus. Est laborum quam aliquam qui sunt. Ut ea et qui provident voluptatibus. Eius quam odit sint cumque sint. Corporis quia et dicta.</p>\`}
598+
getCodeBlockAriaLabel={(codeName) => \`\${codeName} code example\`}
599+
/>
600+
),
601+
},
602+
{
603+
label: "Option 2",
604+
value: 2,
605+
feedback:
606+
"Recusandae necessitatibus consequatur voluptatem sapiente.",
607+
},
608+
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
609+
],
610+
correctAnswer: 1,
611+
},
612+
{
613+
question: "Consectetur adipiscing elit",
614+
answers: [
615+
{
616+
label: "Option 1",
617+
value: 1,
618+
feedback: (
619+
<PrismFormatted
620+
text={\`<p>Quaerat in autem sapiente illum. Vel mollitia omnis qui dolorem <code>um</code> esse eos maiores possimus. Est laborum quam aliquam qui sunt. Ut ea et qui provident voluptatibus. Eius quam odit sint cumque sint. Corporis quia et dicta.</p>\`}
621+
getCodeBlockAriaLabel={(codeName) => \`\${codeName} code example\`}
622+
/>
623+
),
624+
},
625+
{
626+
label: "Option 2",
627+
value: 2,
628+
feedback:
629+
"Recusandae necessitatibus consequatur voluptatem sapiente.",
630+
},
631+
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
632+
],
633+
correctAnswer: 2,
634+
},
635+
{
636+
question: "Fugit itaque delectus voluptatem alias aliquid",
637+
answers: [
638+
{
639+
label: "Option 1",
640+
value: 1,
641+
feedback: (
642+
<PrismFormatted
643+
text={\`<p>Quaerat in autem sapiente illum. Vel mollitia omnis qui dolorem <code>um</code> esse eos maiores possimus. Est laborum quam aliquam qui sunt. Ut ea et qui provident voluptatibus. Eius quam odit sint cumque sint. Corporis quia et dicta.</p>\`}
644+
getCodeBlockAriaLabel={(codeName) => \`\${codeName} code example\`}
645+
/>
646+
),
647+
},
648+
{
649+
label: "Option 2",
650+
value: 2,
651+
feedback:
652+
"Recusandae necessitatibus consequatur voluptatem sapiente.",
653+
},
654+
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
655+
],
656+
correctAnswer: 3,
657+
},
658+
];
659+
660+
const App = () => {
661+
const { questions, validateAnswers } = useQuiz({
662+
initialQuestions,
663+
validationMessages: {
664+
correct: "Correct.",
665+
incorrect: "Incorrect.",
666+
},
667+
passingGrade: 50,
668+
showCorrectAnswersOnSuccess: true
669+
});
670+
671+
const [disabled, setDisabled] = useState(false);
672+
673+
const handleSubmit = () => {
674+
validateAnswers();
675+
setDisabled(true);
676+
};
677+
678+
return (
679+
<div>
680+
<div aria-live="polite">
681+
{!!correctAnswerCount && (
682+
<p className="text-foreground-primary">
683+
Correct answers: {correctAnswerCount}
684+
</p>
685+
)}
686+
</div>
687+
<Quiz questions={questions} disabled={disabled} />
688+
<Spacer size="m" />
689+
<Button onClick={handleSubmit}>Submit</Button>
690+
</div>
691+
);
692+
};`,
693+
},
694+
},
695+
},
696+
};
697+
474698
export default story;

src/quiz/use-quiz.test.ts

+99
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,105 @@ describe("useQuiz", () => {
197197
expect(result.current.validated).toBe(true);
198198
});
199199

200+
it("should return the questions array with the correct validation status if `showCorrectAnswersOnSuccess` is `true`", () => {
201+
const { result } = renderHook(() =>
202+
useQuiz({
203+
initialQuestions: [
204+
{
205+
question: "Lorem ipsum dolor sit amet",
206+
answers: [
207+
{ label: "Option 1", value: 1 },
208+
{ label: "Option 2", value: 2 },
209+
{ label: "Option 3", value: 3 },
210+
],
211+
selectedAnswer: 1,
212+
correctAnswer: 1,
213+
},
214+
{
215+
question: "Consectetur adipiscing elit",
216+
answers: [
217+
{ label: "Option 1", value: 1 },
218+
{ label: "Option 2", value: 2 },
219+
{ label: "Option 3", value: 3 },
220+
],
221+
selectedAnswer: 3,
222+
correctAnswer: 2,
223+
},
224+
],
225+
validationMessages,
226+
passingGrade: 50,
227+
showCorrectAnswersOnSuccess: true,
228+
}),
229+
);
230+
231+
expect(result.current.validated).toBe(false);
232+
expect(result.current.correctAnswerCount).toBeUndefined();
233+
expect(result.current.grade).toBeUndefined();
234+
235+
act(() => {
236+
result.current.validateAnswers();
237+
});
238+
239+
expect(result.current.questions).toMatchObject([
240+
{
241+
question: "Lorem ipsum dolor sit amet",
242+
answers: [
243+
{
244+
label: "Option 1",
245+
value: 1,
246+
validation: {
247+
message: "Correct",
248+
state: "correct",
249+
},
250+
},
251+
{
252+
label: "Option 2",
253+
value: 2,
254+
},
255+
{
256+
label: "Option 3",
257+
value: 3,
258+
},
259+
],
260+
onChange: expect.any(Function),
261+
selectedAnswer: 1,
262+
correctAnswer: 1,
263+
},
264+
{
265+
question: "Consectetur adipiscing elit",
266+
answers: [
267+
{
268+
label: "Option 1",
269+
value: 1,
270+
},
271+
{
272+
label: "Option 2",
273+
value: 2,
274+
validation: {
275+
message: "Correct",
276+
state: "correct",
277+
},
278+
},
279+
{
280+
label: "Option 3",
281+
value: 3,
282+
validation: {
283+
message: "Incorrect",
284+
state: "incorrect",
285+
},
286+
},
287+
],
288+
onChange: expect.any(Function),
289+
selectedAnswer: 3,
290+
correctAnswer: 2,
291+
},
292+
]);
293+
294+
expect(result.current.correctAnswerCount).toBe(1);
295+
expect(result.current.grade).toBe(50);
296+
expect(result.current.validated).toBe(true);
297+
});
298+
200299
it("should call the `onSuccess` function if the quiz results meet the passing grade", () => {
201300
const onSuccess = jest.fn();
202301
const onFailure = jest.fn();

0 commit comments

Comments
 (0)