Skip to content

feat(quiz): allow revealing correct answers on success #487

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions src/quiz-question/answer.tsx
Original file line number Diff line number Diff line change
@@ -113,9 +113,9 @@ export const Answer = <AnswerT extends number | string>({
const getRadioWrapperCls = () => {
const cls = [...radioWrapperDefaultClasses];

if (checked && validation?.state === "correct")
if (validation?.state === "correct")
cls.push("border-l-background-success");
if (checked && validation?.state === "incorrect")
if (validation?.state === "incorrect")
cls.push("border-l-background-danger");

return cls.join(" ");
2 changes: 1 addition & 1 deletion src/quiz-question/quiz-question.tsx
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ export const QuizQuestion = <AnswerT extends number | string>({
feedback={checked && validation && feedback}
checked={checked}
disabled={disabled}
validation={checked ? validation : undefined}
validation={validation}
/>
);
})}
224 changes: 224 additions & 0 deletions src/quiz/quiz.stories.tsx
Original file line number Diff line number Diff line change
@@ -224,6 +224,111 @@ const QuizWithValidationAndAnswerFeedback = () => {
);
};

const QuizWithCorrectAnswersShownOnSuccess = () => {
const initialQuestions: Question<number>[] = [
{
question: "Lorem ipsum dolor sit amet",
answers: [
{
label: "Option 1",
value: 1,
feedback: (
<PrismFormatted
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>`}
getCodeBlockAriaLabel={(codeName) => `${codeName} code example`}
/>
),
},
{
label: "Option 2",
value: 2,
feedback:
"Recusandae necessitatibus consequatur voluptatem sapiente.",
},
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
],
correctAnswer: 1,
},
{
question: "Consectetur adipiscing elit",
answers: [
{
label: "Option 1",
value: 1,
feedback: (
<PrismFormatted
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>`}
getCodeBlockAriaLabel={(codeName) => `${codeName} code example`}
/>
),
},
{
label: "Option 2",
value: 2,
feedback:
"Recusandae necessitatibus consequatur voluptatem sapiente.",
},
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
],
correctAnswer: 2,
},
{
question: "Fugit itaque delectus voluptatem alias aliquid",
answers: [
{
label: "Option 1",
value: 1,
feedback: (
<PrismFormatted
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>`}
getCodeBlockAriaLabel={(codeName) => `${codeName} code example`}
/>
),
},
{
label: "Option 2",
value: 2,
feedback:
"Recusandae necessitatibus consequatur voluptatem sapiente.",
},
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
],
correctAnswer: 3,
},
];

const { questions, validateAnswers, correctAnswerCount } = useQuiz({
initialQuestions,
validationMessages: {
correct: "Correct.",
incorrect: "Incorrect.",
},
passingPercent: 50,
showCorrectAnswersOnSuccess: true,
});
const [disabled, setDisabled] = useState(false);

const handleSubmit = () => {
validateAnswers();
setDisabled(true);
};

return (
<div>
<div aria-live="polite">
{!!correctAnswerCount && (
<p className="text-foreground-primary">
Correct answers: {correctAnswerCount}
</p>
)}
</div>
<Quiz questions={questions} disabled={disabled} />
<Spacer size="m" />
<Button onClick={handleSubmit}>Submit</Button>
</div>
);
};

export const Default: Story = {
render: QuizDefault,
args: {},
@@ -471,4 +576,123 @@ const App = () => {
},
};

export const WithCorrectAnswersShownOnSuccess: Story = {
render: QuizWithCorrectAnswersShownOnSuccess,
args: {},
parameters: {
docs: {
source: {
code: `
import { Quiz, useQuiz, Button, Spacer } from '@freecodecamp/ui';

const initialQuestions = [
{
question: "Lorem ipsum dolor sit amet",
answers: [
{
label: "Option 1",
value: 1,
feedback: (
<PrismFormatted
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>\`}
getCodeBlockAriaLabel={(codeName) => \`\${codeName} code example\`}
/>
),
},
{
label: "Option 2",
value: 2,
feedback:
"Recusandae necessitatibus consequatur voluptatem sapiente.",
},
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
],
correctAnswer: 1,
},
{
question: "Consectetur adipiscing elit",
answers: [
{
label: "Option 1",
value: 1,
feedback: (
<PrismFormatted
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>\`}
getCodeBlockAriaLabel={(codeName) => \`\${codeName} code example\`}
/>
),
},
{
label: "Option 2",
value: 2,
feedback:
"Recusandae necessitatibus consequatur voluptatem sapiente.",
},
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
],
correctAnswer: 2,
},
{
question: "Fugit itaque delectus voluptatem alias aliquid",
answers: [
{
label: "Option 1",
value: 1,
feedback: (
<PrismFormatted
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>\`}
getCodeBlockAriaLabel={(codeName) => \`\${codeName} code example\`}
/>
),
},
{
label: "Option 2",
value: 2,
feedback:
"Recusandae necessitatibus consequatur voluptatem sapiente.",
},
{ label: "Option 3", value: 3, feedback: "Voluptas et et animi quo." },
],
correctAnswer: 3,
},
];

const App = () => {
const { questions, validateAnswers } = useQuiz({
initialQuestions,
validationMessages: {
correct: "Correct.",
incorrect: "Incorrect.",
},
passingPercent: 50,
showCorrectAnswersOnSuccess: true
});

const [disabled, setDisabled] = useState(false);

const handleSubmit = () => {
validateAnswers();
setDisabled(true);
};

return (
<div>
<div aria-live="polite">
{!!correctAnswerCount && (
<p className="text-foreground-primary">
Correct answers: {correctAnswerCount}
</p>
)}
</div>
<Quiz questions={questions} disabled={disabled} />
<Spacer size="m" />
<Button onClick={handleSubmit}>Submit</Button>
</div>
);
};`,
},
},
},
};

export default story;
99 changes: 99 additions & 0 deletions src/quiz/use-quiz.test.ts
Original file line number Diff line number Diff line change
@@ -197,6 +197,105 @@ describe("useQuiz", () => {
expect(result.current.validated).toBe(true);
});

it("should return the questions array with the correct validation status if `showCorrectAnswersOnSuccess` is `true`", () => {
const { result } = renderHook(() =>
useQuiz({
initialQuestions: [
{
question: "Lorem ipsum dolor sit amet",
answers: [
{ label: "Option 1", value: 1 },
{ label: "Option 2", value: 2 },
{ label: "Option 3", value: 3 },
],
selectedAnswer: 1,
correctAnswer: 1,
},
{
question: "Consectetur adipiscing elit",
answers: [
{ label: "Option 1", value: 1 },
{ label: "Option 2", value: 2 },
{ label: "Option 3", value: 3 },
],
selectedAnswer: 3,
correctAnswer: 2,
},
],
validationMessages,
passingPercent: 50,
showCorrectAnswersOnSuccess: true,
}),
);

expect(result.current.validated).toBe(false);
expect(result.current.correctAnswerCount).toBeUndefined();
expect(result.current.grade).toBeUndefined();

act(() => {
result.current.validateAnswers();
});

expect(result.current.questions).toMatchObject([
{
question: "Lorem ipsum dolor sit amet",
answers: [
{
label: "Option 1",
value: 1,
validation: {
message: "Correct",
state: "correct",
},
},
{
label: "Option 2",
value: 2,
},
{
label: "Option 3",
value: 3,
},
],
onChange: expect.any(Function),
selectedAnswer: 1,
correctAnswer: 1,
},
{
question: "Consectetur adipiscing elit",
answers: [
{
label: "Option 1",
value: 1,
},
{
label: "Option 2",
value: 2,
validation: {
message: "Correct",
state: "correct",
},
},
{
label: "Option 3",
value: 3,
validation: {
message: "Incorrect",
state: "incorrect",
},
},
],
onChange: expect.any(Function),
selectedAnswer: 3,
correctAnswer: 2,
},
]);

expect(result.current.correctAnswerCount).toBe(1);
expect(result.current.grade).toBe(50);
expect(result.current.validated).toBe(true);
});

it("should call the `onSuccess` function if the quiz results meet the passing grade", () => {
const onSuccess = jest.fn();
const onFailure = jest.fn();
Loading