mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2025-12-19 18:18:27 -05:00
fix(client,challenge-parser): display highlighted text as span for language challenges (#63802)
This commit is contained in:
@@ -469,6 +469,19 @@ form {
|
||||
input:focus {
|
||||
border-color: var(--tertiary-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* Text within backticks in en-US, es, and zh-CN challenge files
|
||||
* is parsed into spans with this class.
|
||||
*/
|
||||
.highlighted-text {
|
||||
padding: 1px 4px;
|
||||
background-color: var(--tertiary-background);
|
||||
color: var(--tertiary-color);
|
||||
border: 1px solid var(--gray-45);
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 1px 4px;
|
||||
background-color: var(--tertiary-background);
|
||||
|
||||
36
tools/challenge-parser/parser/__fixtures__/with-en-us-mcq.md
Normal file
36
tools/challenge-parser/parser/__fixtures__/with-en-us-mcq.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: with-en-us-mcq
|
||||
title: en-US MCQ
|
||||
challengeType: 19
|
||||
lang: en-US
|
||||
---
|
||||
|
||||
# --instructions--
|
||||
|
||||
Instructions containing `some code`.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Question text containing `highlighted text`.
|
||||
|
||||
## --answers--
|
||||
|
||||
`correct answer`
|
||||
|
||||
---
|
||||
|
||||
`wrong answer`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback text containing `highlighted text`.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
|
||||
# --explanation--
|
||||
|
||||
Explanation text containing `highlighted text`.
|
||||
36
tools/challenge-parser/parser/__fixtures__/with-es-mcq.md
Normal file
36
tools/challenge-parser/parser/__fixtures__/with-es-mcq.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: with-es-mcq
|
||||
title: Spanish MCQ
|
||||
challengeType: 19
|
||||
lang: es
|
||||
---
|
||||
|
||||
# --instructions--
|
||||
|
||||
Instructions containing `texto resaltado`.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Question text containing `texto resaltado`.
|
||||
|
||||
## --answers--
|
||||
|
||||
`correct answer`
|
||||
|
||||
---
|
||||
|
||||
`wrong answer`
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback text containing `texto resaltado`.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
|
||||
# --explanation--
|
||||
|
||||
Explanation text containing `texto resaltado`.
|
||||
@@ -103,36 +103,36 @@ describe('add-quizzes plugin', () => {
|
||||
|
||||
// Quiz 1, Question 2
|
||||
expect(secondQuestion.text).toBe(
|
||||
'<p>Quiz 1, question 2 with <code>中文</code></p>'
|
||||
'<p>Quiz 1, question 2 with <span class="highlighted-text">中文</span></p>'
|
||||
);
|
||||
expect(secondQuestion.distractors[0]).toBe(
|
||||
'<p>Quiz 1, question 2, distractor 1 with <code>中文</code></p>'
|
||||
'<p>Quiz 1, question 2, distractor 1 with <span class="highlighted-text">中文</span></p>'
|
||||
);
|
||||
expect(secondQuestion.distractors[1]).toBe(
|
||||
'<p>Quiz 1, question 2, distractor 2 with <code>中文</code></p>'
|
||||
'<p>Quiz 1, question 2, distractor 2 with <span class="highlighted-text">中文</span></p>'
|
||||
);
|
||||
expect(secondQuestion.distractors[2]).toBe(
|
||||
'<p>Quiz 1, question 2, distractor 3 with <code>中文</code></p>'
|
||||
'<p>Quiz 1, question 2, distractor 3 with <span class="highlighted-text">中文</span></p>'
|
||||
);
|
||||
expect(secondQuestion.answer).toBe(
|
||||
'<p>Quiz 1, question 2, answer with <code>中文</code></p>'
|
||||
'<p>Quiz 1, question 2, answer with <span class="highlighted-text">中文</span></p>'
|
||||
);
|
||||
|
||||
// Quiz 1, Question 3
|
||||
expect(thirdQuestion.text).toBe(
|
||||
'<p>Quiz 1, question 3 with <code>zhōng wén</code></p>'
|
||||
'<p>Quiz 1, question 3 with <span class="highlighted-text">zhōng wén</span></p>'
|
||||
);
|
||||
expect(thirdQuestion.distractors[0]).toBe(
|
||||
'<p>Quiz 1, question 3, distractor 1 with <code>zhōng wén</code></p>'
|
||||
'<p>Quiz 1, question 3, distractor 1 with <span class="highlighted-text">zhōng wén</span></p>'
|
||||
);
|
||||
expect(thirdQuestion.distractors[1]).toBe(
|
||||
'<p>Quiz 1, question 3, distractor 2 with <code>zhōng wén</code></p>'
|
||||
'<p>Quiz 1, question 3, distractor 2 with <span class="highlighted-text">zhōng wén</span></p>'
|
||||
);
|
||||
expect(thirdQuestion.distractors[2]).toBe(
|
||||
'<p>Quiz 1, question 3, distractor 3 with <code>zhōng wén</code></p>'
|
||||
'<p>Quiz 1, question 3, distractor 3 with <span class="highlighted-text">zhōng wén</span></p>'
|
||||
);
|
||||
expect(thirdQuestion.answer).toBe(
|
||||
'<p>Quiz 1, question 3, answer with <code>zhōng wén</code></p>'
|
||||
'<p>Quiz 1, question 3, answer with <span class="highlighted-text">zhōng wén</span></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,9 @@ describe('add-text', () => {
|
||||
mockAST,
|
||||
withSubSectionAST,
|
||||
withNestedInstructionsAST,
|
||||
withChineseAST;
|
||||
withChineseAST,
|
||||
withEnUsAST,
|
||||
withEsAST;
|
||||
const descriptionId = 'description';
|
||||
const instructionsId = 'instructions';
|
||||
const missingId = 'missing';
|
||||
@@ -21,6 +23,8 @@ describe('add-text', () => {
|
||||
'with-nested-instructions.md'
|
||||
);
|
||||
withChineseAST = await parseFixture('with-chinese-mcq.md');
|
||||
withEnUsAST = await parseFixture('with-en-us-mcq.md');
|
||||
withEsAST = await parseFixture('with-es-mcq.md');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -162,6 +166,20 @@ describe('add-text', () => {
|
||||
expect(file.data).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render inline code as code elements when lang is undefined', () => {
|
||||
const plugin = addText(['instructions', 'explanation']);
|
||||
|
||||
const defaultFile = { data: {} };
|
||||
plugin(withChineseAST, defaultFile);
|
||||
|
||||
expect(defaultFile.data.instructions).toBe(
|
||||
'<section id="instructions">\n<p>Instructions containing <code>汉字 (hàn zì)</code>.</p>\n</section>'
|
||||
);
|
||||
expect(defaultFile.data.explanation).toBe(
|
||||
'<section id="explanation">\n<p><code>我是 (wǒ shì) Web 开发者 (kāi fā zhě)。</code> – I am a web developer.</p>\n<p><code>你好 (nǐ hǎo),我是王华 (wǒ shì Wang Hua),请问你叫什么名字 (qǐng wèn nǐ jiào shén me míng zi)?</code> – Hello, I am Wang Hua, may I ask what your name is?</p>\n</section>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render Chinese inline code as ruby when lang is zh-CN', () => {
|
||||
const plugin = addText(['instructions', 'explanation']);
|
||||
|
||||
@@ -175,4 +193,32 @@ describe('add-text', () => {
|
||||
'<section id="explanation">\n<p><ruby>我是<rp>(</rp><rt>wǒ shì</rt><rp>)</rp></ruby> Web <ruby>开发者<rp>(</rp><rt>kāi fā zhě</rt><rp>)</rp></ruby>。 – I am a web developer.</p>\n<p><ruby>你好<rp>(</rp><rt>nǐ hǎo</rt><rp>)</rp></ruby>,<ruby>我是王华<rp>(</rp><rt>wǒ shì Wang Hua</rt><rp>)</rp></ruby>,<ruby>请问你叫什么名字<rp>(</rp><rt>qǐng wèn nǐ jiào shén me míng zi</rt><rp>)</rp></ruby>? – Hello, I am Wang Hua, may I ask what your name is?</p>\n</section>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render inline code as span elements when lang is en-US', () => {
|
||||
const plugin = addText(['instructions', 'explanation']);
|
||||
|
||||
const enUsFile = { data: { lang: 'en-US' } };
|
||||
plugin(withEnUsAST, enUsFile);
|
||||
|
||||
expect(enUsFile.data.instructions).toBe(
|
||||
'<section id="instructions">\n<p>Instructions containing <span class="highlighted-text">some code</span>.</p>\n</section>'
|
||||
);
|
||||
expect(enUsFile.data.explanation).toBe(
|
||||
'<section id="explanation">\n<p>Explanation text containing <span class="highlighted-text">highlighted text</span>.</p>\n</section>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render inline code as span elements when lang is es', () => {
|
||||
const plugin = addText(['instructions', 'explanation']);
|
||||
|
||||
const esFile = { data: { lang: 'es' } };
|
||||
plugin(withEsAST, esFile);
|
||||
|
||||
expect(esFile.data.instructions).toBe(
|
||||
'<section id="instructions">\n<p>Instructions containing <span class="highlighted-text">texto resaltado</span>.</p>\n</section>'
|
||||
);
|
||||
expect(esFile.data.explanation).toBe(
|
||||
'<section id="explanation">\n<p>Explanation text containing <span class="highlighted-text">texto resaltado</span>.</p>\n</section>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,10 +8,12 @@ describe('add-video-question plugin', () => {
|
||||
multipleQuestionAST,
|
||||
videoOutOfOrderAST,
|
||||
videoWithAudioAST,
|
||||
chineseVideoAST,
|
||||
enUsVideoAST,
|
||||
esVideoAST,
|
||||
videoWithSolutionAboveNumberOfAnswersAST,
|
||||
videoWithFeedbackTwiceInARow,
|
||||
videoWithCorrectAnswerWithFeedback,
|
||||
chineseVideoAST;
|
||||
videoWithCorrectAnswerWithFeedback;
|
||||
const plugin = addVideoQuestion();
|
||||
let file = { data: {} };
|
||||
|
||||
@@ -35,6 +37,8 @@ describe('add-video-question plugin', () => {
|
||||
'with-video-question-correct-answer-with-feedback.md'
|
||||
);
|
||||
chineseVideoAST = await parseFixture('with-chinese-mcq.md');
|
||||
enUsVideoAST = await parseFixture('with-en-us-mcq.md');
|
||||
esVideoAST = await parseFixture('with-es-mcq.md');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -211,4 +215,54 @@ describe('add-video-question plugin', () => {
|
||||
'<ruby>问<rp>(</rp><rt>wèn</rt><rp>)</rp></ruby>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render inline code as spans in question text, answers, and feedback for en-US', async () => {
|
||||
const enUsFile = { data: { lang: 'en-US' } };
|
||||
|
||||
plugin(enUsVideoAST, enUsFile);
|
||||
|
||||
const question = enUsFile.data.questions[0];
|
||||
|
||||
expect(question.text).toBe(
|
||||
'<p>Question text containing <span class="highlighted-text">highlighted text</span>.</p>'
|
||||
);
|
||||
|
||||
const answer1 = question.answers[0];
|
||||
expect(answer1.answer).toBe(
|
||||
'<p><span class="highlighted-text">correct answer</span></p>'
|
||||
);
|
||||
|
||||
const answer2 = question.answers[1];
|
||||
expect(answer2.answer).toBe(
|
||||
'<p><span class="highlighted-text">wrong answer</span></p>'
|
||||
);
|
||||
expect(answer2.feedback).toBe(
|
||||
'<p>Feedback text containing <span class="highlighted-text">highlighted text</span>.</p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render inline code as spans in question text, answers, and feedback for es', async () => {
|
||||
const esFile = { data: { lang: 'es' } };
|
||||
|
||||
plugin(esVideoAST, esFile);
|
||||
|
||||
const question = esFile.data.questions[0];
|
||||
|
||||
expect(question.text).toBe(
|
||||
'<p>Question text containing <span class="highlighted-text">texto resaltado</span>.</p>'
|
||||
);
|
||||
|
||||
const answer1 = question.answers[0];
|
||||
expect(answer1.answer).toBe(
|
||||
'<p><span class="highlighted-text">correct answer</span></p>'
|
||||
);
|
||||
|
||||
const answer2 = question.answers[1];
|
||||
expect(answer2.answer).toBe(
|
||||
'<p><span class="highlighted-text">wrong answer</span></p>'
|
||||
);
|
||||
expect(answer2.feedback).toBe(
|
||||
'<p>Feedback text containing <span class="highlighted-text">texto resaltado</span>.</p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,22 +91,48 @@ function chineseInlineCodeHandler(state, node) {
|
||||
// If static text, return code
|
||||
return {
|
||||
type: 'element',
|
||||
// TODO: change this to span
|
||||
// https://github.com/freeCodeCamp/language-curricula/issues/22
|
||||
tagName: 'code',
|
||||
properties: {},
|
||||
tagName: 'span',
|
||||
properties: { className: 'highlighted-text' },
|
||||
children: [{ type: 'text', value: node.value }]
|
||||
};
|
||||
}
|
||||
|
||||
const rubyOptions = {
|
||||
/**
|
||||
* Custom handler for inline code to render as span elements
|
||||
* @param {object} state - The state object from mdast-util-to-hast
|
||||
* @param {object} node - The inlineCode node
|
||||
* @returns {object} Hast element node
|
||||
*/
|
||||
function spanInlineCodeHandler(state, node) {
|
||||
return {
|
||||
type: 'element',
|
||||
tagName: 'span',
|
||||
properties: { className: 'highlighted-text' },
|
||||
children: [{ type: 'text', value: node.value }]
|
||||
};
|
||||
}
|
||||
|
||||
const spanOrRubyOptions = {
|
||||
handlers: {
|
||||
inlineCode: chineseInlineCodeHandler
|
||||
}
|
||||
};
|
||||
|
||||
const createMdastToHtml = lang =>
|
||||
lang == 'zh-CN' ? x => mdastToHTML(x, rubyOptions) : mdastToHTML;
|
||||
const spanOptions = {
|
||||
handlers: {
|
||||
inlineCode: spanInlineCodeHandler
|
||||
}
|
||||
};
|
||||
|
||||
const createMdastToHtml = lang => {
|
||||
if (lang === 'zh-CN') {
|
||||
return x => mdastToHTML(x, spanOrRubyOptions);
|
||||
} else if (lang === 'en-US' || lang === 'es') {
|
||||
return x => mdastToHTML(x, spanOptions);
|
||||
} else {
|
||||
return mdastToHTML;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parseHanziPinyinPairs,
|
||||
|
||||
@@ -198,7 +198,7 @@ describe('createMdastToHtml', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should fallback to code element if pattern does not match', () => {
|
||||
it('should fallback to span element if pattern does not match', () => {
|
||||
const toHtml = createMdastToHtml('zh-CN');
|
||||
const nodes = [
|
||||
{
|
||||
@@ -210,11 +210,49 @@ describe('createMdastToHtml', () => {
|
||||
]
|
||||
}
|
||||
];
|
||||
const actual = toHtml(nodes, { lang: 'zh-CN' });
|
||||
expect(actual).toBe('<p><code>你好</code> and <code>nǐ hǎo</code></p>');
|
||||
const actual = toHtml(nodes);
|
||||
expect(actual).toBe(
|
||||
'<p><span class="highlighted-text">你好</span> and <span class="highlighted-text">nǐ hǎo</span></p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render as regular code when lang is not zh-CN', () => {
|
||||
it('should render inline code as span when lang is en-US', () => {
|
||||
const toHtml = createMdastToHtml('en-US');
|
||||
const nodes = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{ type: 'text', value: 'This is ' },
|
||||
{ type: 'inlineCode', value: 'highlighted text' },
|
||||
{ type: 'text', value: '.' }
|
||||
]
|
||||
}
|
||||
];
|
||||
const actual = toHtml(nodes);
|
||||
expect(actual).toBe(
|
||||
'<p>This is <span class="highlighted-text">highlighted text</span>.</p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render inline code as span when lang is es', () => {
|
||||
const toHtml = createMdastToHtml('es');
|
||||
const nodes = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{ type: 'text', value: 'Esto texto ' },
|
||||
{ type: 'inlineCode', value: 'está resaltado' },
|
||||
{ type: 'text', value: '.' }
|
||||
]
|
||||
}
|
||||
];
|
||||
const actual = toHtml(nodes);
|
||||
expect(actual).toBe(
|
||||
'<p>Esto texto <span class="highlighted-text">está resaltado</span>.</p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render as regular code when lang is not zh-CN, en-US, or es', () => {
|
||||
const toHtml = createMdastToHtml('zh');
|
||||
const nodes = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user