fix(client,curriculum): mark inputType as required for Chinese FITB challenges (#67221)

This commit is contained in:
Huyen Nguyen
2026-05-28 06:58:12 +07:00
committed by GitHub
parent 64945a57a2
commit 32713ed842
8 changed files with 20 additions and 24 deletions
-1
View File
@@ -540,7 +540,6 @@ type ChallengeNodeChallengeSceneCommandsPosition {
type ChallengeNodeChallengeFillInTheBlank @derivedTypes { type ChallengeNodeChallengeFillInTheBlank @derivedTypes {
sentence: String sentence: String
blanks: [ChallengeNodeChallengeFillInTheBlankBlanks] blanks: [ChallengeNodeChallengeFillInTheBlankBlanks]
inputType: String
} }
type ChallengeNodeChallengeFillInTheBlankBlanks { type ChallengeNodeChallengeFillInTheBlankBlanks {
+3 -1
View File
@@ -43,9 +43,10 @@ export type Question = {
export type FillInTheBlank = { export type FillInTheBlank = {
sentence: string; sentence: string;
blanks: MultipleChoiceAnswer[]; blanks: MultipleChoiceAnswer[];
inputType?: 'pinyin-tone' | 'pinyin-to-hanzi';
}; };
export type FillInTheBlankInputType = 'pinyin-tone' | 'pinyin-to-hanzi';
export type Fields = { export type Fields = {
slug: string; slug: string;
blockHashSlug: string; blockHashSlug: string;
@@ -216,6 +217,7 @@ export type ChallengeNode = {
helpCategory: string; helpCategory: string;
hooks?: Hooks; hooks?: Hooks;
id: string; id: string;
inputType?: FillInTheBlankInputType;
lang?: ChallengeLang; lang?: ChallengeLang;
instructions: string; instructions: string;
internal?: { internal?: {
@@ -4,13 +4,17 @@ import { Spacer } from '@freecodecamp/ui';
import { parseBlanks, parseAnswer } from '../fill-in-the-blank/parse-blanks'; import { parseBlanks, parseAnswer } from '../fill-in-the-blank/parse-blanks';
import PrismFormatted from '../components/prism-formatted'; import PrismFormatted from '../components/prism-formatted';
import { FillInTheBlank } from '../../../redux/prop-types'; import {
FillInTheBlankInputType,
FillInTheBlank
} from '../../../redux/prop-types';
import ChallengeHeading from './challenge-heading'; import ChallengeHeading from './challenge-heading';
import PinyinToHanziInput from './pinyin-to-hanzi-input'; import PinyinToHanziInput from './pinyin-to-hanzi-input';
import PinyinToneInput from './pinyin-tone-input'; import PinyinToneInput from './pinyin-tone-input';
type FillInTheBlankProps = { type FillInTheBlankProps = {
fillInTheBlank: FillInTheBlank; fillInTheBlank: FillInTheBlank;
inputType?: FillInTheBlankInputType;
answersCorrect: (boolean | null)[]; answersCorrect: (boolean | null)[];
showFeedback: boolean; showFeedback: boolean;
feedback: string | null; feedback: string | null;
@@ -103,7 +107,8 @@ const BlankInput = ({
}; };
function FillInTheBlanks({ function FillInTheBlanks({
fillInTheBlank: { sentence, blanks, inputType }, fillInTheBlank: { sentence, blanks },
inputType,
answersCorrect, answersCorrect,
showFeedback, showFeedback,
feedback, feedback,
@@ -92,6 +92,7 @@ const ShowFillInTheBlank = ({
translationPending, translationPending,
challengeType, challengeType,
fillInTheBlank, fillInTheBlank,
inputType,
helpCategory, helpCategory,
scene, scene,
tests, tests,
@@ -181,7 +182,7 @@ const ShowFillInTheBlank = ({
const answer = blankAnswers[i]; const answer = blankAnswers[i];
const normalizedUserAnswer = userAnswer.trim().toLowerCase(); const normalizedUserAnswer = userAnswer.trim().toLowerCase();
if (fillInTheBlank.inputType === 'pinyin-to-hanzi') { if (inputType === 'pinyin-to-hanzi') {
const pairs = parseHanziPinyinPairs(answer); const pairs = parseHanziPinyinPairs(answer);
if (pairs.length === 1) { if (pairs.length === 1) {
const hanziPinyin = pairs[0]; const hanziPinyin = pairs[0];
@@ -191,7 +192,7 @@ const ShowFillInTheBlank = ({
hanzi.replace(/\s+/g, '') hanzi.replace(/\s+/g, '')
); );
} }
} else if (fillInTheBlank.inputType === 'pinyin-tone') { } else if (inputType === 'pinyin-tone') {
// Ignore spaces to allow both syllable formats: // Ignore spaces to allow both syllable formats:
// spaced (e.g., 'nǐ hǎo') and unspaced (e.g., 'nǐhǎo'). // spaced (e.g., 'nǐ hǎo') and unspaced (e.g., 'nǐhǎo').
return ( return (
@@ -302,6 +303,7 @@ const ShowFillInTheBlank = ({
<ObserveKeys only={['ctrl', 'cmd', 'enter']}> <ObserveKeys only={['ctrl', 'cmd', 'enter']}>
<FillInTheBlanks <FillInTheBlanks
fillInTheBlank={fillInTheBlank} fillInTheBlank={fillInTheBlank}
inputType={inputType}
answersCorrect={answersCorrect} answersCorrect={answersCorrect}
showFeedback={showFeedback} showFeedback={showFeedback}
feedback={feedback} feedback={feedback}
@@ -371,8 +373,8 @@ export const query = graphql`
answer answer
feedback feedback
} }
inputType
} }
inputType
tests { tests {
text text
testString testString
@@ -476,17 +476,6 @@ exports[`challenge schema > should not be changed without informing the mobile t
], ],
"type": "array", "type": "array",
}, },
"inputType": {
"allow": [
"pinyin-tone",
"pinyin-to-hanzi",
],
"flags": {
"only": true,
"presence": "optional",
},
"type": "string",
},
"sentence": { "sentence": {
"flags": { "flags": {
"presence": "required", "presence": "required",
@@ -629,7 +618,7 @@ exports[`challenge schema > should not be changed without informing the mobile t
], ],
"flags": { "flags": {
"only": true, "only": true,
"presence": "optional", "presence": "required",
}, },
"type": "string", "type": "string",
}, },
+2 -3
View File
@@ -248,8 +248,7 @@ export const schema = Joi.object().keys({
feedback: Joi.string().allow(null) feedback: Joi.string().allow(null)
}) })
) )
.required(), .required()
inputType: Joi.string().valid('pinyin-tone', 'pinyin-to-hanzi').optional()
}), }),
forumTopicId: Joi.number(), forumTopicId: Joi.number(),
id: Joi.objectId().required(), id: Joi.objectId().required(),
@@ -262,7 +261,7 @@ export const schema = Joi.object().keys({
is: challengeTypes.fillInTheBlank, is: challengeTypes.fillInTheBlank,
then: Joi.when('superBlock', { then: Joi.when('superBlock', {
is: Joi.valid(SuperBlocks.A1Chinese, SuperBlocks.A2Chinese), is: Joi.valid(SuperBlocks.A1Chinese, SuperBlocks.A2Chinese),
then: Joi.string().valid('pinyin-tone', 'pinyin-to-hanzi').optional(), then: Joi.string().valid('pinyin-tone', 'pinyin-to-hanzi').required(),
otherwise: Joi.forbidden() otherwise: Joi.forbidden()
}), }),
otherwise: Joi.forbidden() otherwise: Joi.forbidden()
@@ -107,7 +107,7 @@ function plugin() {
} }
} }
return { sentence, blanks, ...(inputType && { inputType }) }; return { sentence, blanks };
} }
function getBlanks(blanksNodes) { function getBlanks(blanksNodes) {
@@ -198,7 +198,7 @@ Example of good formatting:
plugin(mockChineseFillInTheBlankAST, file); plugin(mockChineseFillInTheBlankAST, file);
const testObject = file.data.fillInTheBlank; const testObject = file.data.fillInTheBlank;
expect(testObject.inputType).toBe('pinyin-to-hanzi'); expect(file.data.inputType).toBe('pinyin-to-hanzi');
expect(testObject.sentence).toBe( expect(testObject.sentence).toBe(
'<p>BLANK BLANKBLANK <ruby>是王华<rp>(</rp><rt>shì Wang Hua</rt><rp>)</rp></ruby><ruby>请问你<rp>(</rp><rt>qǐng wèn nǐ</rt><rp>)</rp></ruby> BLANK <ruby>什么名字<rp>(</rp><rt>shén me míng zi</rt><rp>)</rp></ruby></p>' '<p>BLANK BLANKBLANK <ruby>是王华<rp>(</rp><rt>shì Wang Hua</rt><rp>)</rp></ruby><ruby>请问你<rp>(</rp><rt>qǐng wèn nǐ</rt><rp>)</rp></ruby> BLANK <ruby>什么名字<rp>(</rp><rt>shén me míng zi</rt><rp>)</rp></ruby></p>'