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();