diff --git a/curriculum/challenges/english/blocks/lecture-working-with-data-fetching-and-memoization-in-react/67d2f4ddb4a4306fdf5bbaee.md b/curriculum/challenges/english/blocks/lecture-working-with-data-fetching-and-memoization-in-react/67d2f4ddb4a4306fdf5bbaee.md
index 38fd19e61b9..61a11871646 100644
--- a/curriculum/challenges/english/blocks/lecture-working-with-data-fetching-and-memoization-in-react/67d2f4ddb4a4306fdf5bbaee.md
+++ b/curriculum/challenges/english/blocks/lecture-working-with-data-fetching-and-memoization-in-react/67d2f4ddb4a4306fdf5bbaee.md
@@ -234,10 +234,6 @@ Memoization tools focus on caching values and functions, while this option handl
`useEffect`
-### --feedback--
-
-Memoization tools focus on caching values and functions, while this option handles side effects.
-
## --video-solution--
4
diff --git a/curriculum/schema/challenge-schema.js b/curriculum/schema/challenge-schema.js
index b4b34250ac6..e93ad5256b0 100644
--- a/curriculum/schema/challenge-schema.js
+++ b/curriculum/schema/challenge-schema.js
@@ -99,7 +99,7 @@ const questionJoi = Joi.object().keys({
)
.required()
.unique('answer'),
- solution: Joi.number().required()
+ solution: Joi.number().min(1).max(Joi.ref('..answers.length')).required()
});
const quizJoi = Joi.object().keys({
diff --git a/tools/challenge-parser/parser/__fixtures__/with-video-question-correct-answer-with-feedback.md b/tools/challenge-parser/parser/__fixtures__/with-video-question-correct-answer-with-feedback.md
new file mode 100644
index 00000000000..2f54f3c8d84
--- /dev/null
+++ b/tools/challenge-parser/parser/__fixtures__/with-video-question-correct-answer-with-feedback.md
@@ -0,0 +1,51 @@
+# --description--
+
+Paragraph 1
+
+```html
+code example
+```
+
+# --instructions--
+
+Paragraph 0
+
+```html
+code example 0
+```
+
+# --questions--
+
+## --text--
+
+Question line 1
+
+```js
+ var x = 'y';
+```
+
+## --answers--
+
+Some inline `code`
+
+### --feedback--
+
+That is not correct.
+
+---
+
+Some *italics*
+
+A second answer paragraph.
+
+---
+
+ code in code tags
+
+### --feedback--
+
+Feedback line
+
+## --video-solution--
+
+3
diff --git a/tools/challenge-parser/parser/__fixtures__/with-video-question-feedback-twice-in-a-row.md b/tools/challenge-parser/parser/__fixtures__/with-video-question-feedback-twice-in-a-row.md
new file mode 100644
index 00000000000..332eb9f648c
--- /dev/null
+++ b/tools/challenge-parser/parser/__fixtures__/with-video-question-feedback-twice-in-a-row.md
@@ -0,0 +1,51 @@
+# --description--
+
+Paragraph 1
+
+```html
+code example
+```
+
+# --instructions--
+
+Paragraph 0
+
+```html
+code example 0
+```
+
+# --questions--
+
+## --text--
+
+Question line 1
+
+```js
+ var x = 'y';
+```
+
+## --answers--
+
+Some inline `code`
+
+### --feedback--
+
+That is not correct.
+
+---
+
+Some *italics*
+
+### --feedback--
+
+A second answer paragraph.
+
+Another paragraph.
+
+### --feedback--
+
+ code in code tags
+
+## --video-solution--
+
+1
diff --git a/tools/challenge-parser/parser/__fixtures__/with-video-question-solution-above-number-of-answers.md b/tools/challenge-parser/parser/__fixtures__/with-video-question-solution-above-number-of-answers.md
new file mode 100644
index 00000000000..5cabec9239d
--- /dev/null
+++ b/tools/challenge-parser/parser/__fixtures__/with-video-question-solution-above-number-of-answers.md
@@ -0,0 +1,47 @@
+# --description--
+
+Paragraph 1
+
+```html
+code example
+```
+
+# --instructions--
+
+Paragraph 0
+
+```html
+code example 0
+```
+
+# --questions--
+
+## --text--
+
+Question line 1
+
+```js
+ var x = 'y';
+```
+
+## --answers--
+
+Some inline `code`
+
+### --feedback--
+
+That is not correct.
+
+---
+
+Some *italics*
+
+A second answer paragraph.
+
+---
+
+ code in code tags
+
+## --video-solution--
+
+5
diff --git a/tools/challenge-parser/parser/plugins/add-video-question.js b/tools/challenge-parser/parser/plugins/add-video-question.js
index 2c2cb404ac1..d7822e90b11 100644
--- a/tools/challenge-parser/parser/plugins/add-video-question.js
+++ b/tools/challenge-parser/parser/plugins/add-video-question.js
@@ -1,6 +1,6 @@
const { root } = require('mdast-builder');
const find = require('unist-util-find');
-const { getSection } = require('./utils/get-section');
+const { getSection, getAllSections } = require('./utils/get-section');
const getAllBefore = require('./utils/before-heading');
const { getParagraphContent } = require('./utils/get-paragraph-content');
@@ -20,6 +20,12 @@ function plugin() {
if (!text) throw Error('text is missing from question');
if (!answers) throw Error('answers are missing from question');
if (!solution) throw Error('solution is missing from question');
+ if (solution > answers.length)
+ throw Error(
+ `solution must be within range of number of answers: 1-${answers.length}`
+ );
+ if (answers[solution - 1].feedback)
+ throw Error('answer selected as solution cannot have feedback section');
return { text, answers, solution };
}
@@ -27,11 +33,16 @@ function plugin() {
function getAnswers(answersNodes) {
const answerGroups = splitOnThematicBreak(answersNodes);
- return answerGroups.map(answerGroup => {
+ return answerGroups.map((answerGroup, index) => {
const answerTree = root(answerGroup);
- const feedbackNodes = getSection(answerTree, '--feedback--');
+ const feedbackGroups = getAllSections(answerTree, '--feedback--');
+
+ if (feedbackGroups.length > 1)
+ throw new Error(`answer ${index + 1} has multiple feedback sections`);
+
+ const [feedbackNodes] = feedbackGroups;
const audioIdNodes = getSection(answerTree, '--audio-id--');
- const hasFeedback = feedbackNodes.length > 0;
+ const hasFeedback = feedbackNodes?.length > 0;
const hasAudioId = audioIdNodes.length > 0;
if (hasFeedback || hasAudioId) {
diff --git a/tools/challenge-parser/parser/plugins/add-video-question.test.js b/tools/challenge-parser/parser/plugins/add-video-question.test.js
index 629e0c1cff8..9a2ac598001 100644
--- a/tools/challenge-parser/parser/plugins/add-video-question.test.js
+++ b/tools/challenge-parser/parser/plugins/add-video-question.test.js
@@ -8,6 +8,9 @@ describe('add-video-question plugin', () => {
multipleQuestionAST,
videoOutOfOrderAST,
videoWithAudioAST,
+ videoWithSolutionAboveNumberOfAnswersAST,
+ videoWithFeedbackTwiceInARow,
+ videoWithCorrectAnswerWithFeedback,
chineseVideoAST;
const plugin = addVideoQuestion();
let file = { data: {} };
@@ -22,6 +25,15 @@ describe('add-video-question plugin', () => {
'with-video-question-out-of-order.md'
);
videoWithAudioAST = await parseFixture('with-video-question-audio.md');
+ videoWithSolutionAboveNumberOfAnswersAST = await parseFixture(
+ 'with-video-question-solution-above-number-of-answers.md'
+ );
+ videoWithFeedbackTwiceInARow = await parseFixture(
+ 'with-video-question-feedback-twice-in-a-row.md'
+ );
+ videoWithCorrectAnswerWithFeedback = await parseFixture(
+ 'with-video-question-correct-answer-with-feedback.md'
+ );
chineseVideoAST = await parseFixture('with-chinese-mcq.md');
});
@@ -109,6 +121,27 @@ describe('add-video-question plugin', () => {
);
});
+ it('should throw if solution is higher than the number of answers', () => {
+ expect.assertions(1);
+ expect(() =>
+ plugin(videoWithSolutionAboveNumberOfAnswersAST, file)
+ ).toThrow('solution must be within range of number of answers: 1-3');
+ });
+
+ it('should throw if answer has more than one feedback section', () => {
+ expect.assertions(1);
+ expect(() => plugin(videoWithFeedbackTwiceInARow, file)).toThrow(
+ 'answer 2 has multiple feedback sections'
+ );
+ });
+
+ it('should throw if correct answer has feedback section', () => {
+ expect.assertions(1);
+ expect(() => plugin(videoWithCorrectAnswerWithFeedback, file)).toThrow(
+ 'answer selected as solution cannot have feedback section'
+ );
+ });
+
it('should NOT throw if there is no question', () => {
expect.assertions(1);
expect(() => plugin(simpleAST, file)).not.toThrow();