mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(tools, client): add speaking tasks logic (#61906)
Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# --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.
|
||||
|
||||
### --audio-id--
|
||||
|
||||
answer1-audio
|
||||
|
||||
---
|
||||
|
||||
Some *italics*
|
||||
|
||||
A second answer paragraph.
|
||||
|
||||
### --audio-id--
|
||||
|
||||
answer2-audio
|
||||
|
||||
---
|
||||
|
||||
<code> code in </code> code tags
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
@@ -688,15 +688,18 @@ exports[`challenge parser > should parse video questions 1`] = `
|
||||
"answers": [
|
||||
{
|
||||
"answer": "<p>Some inline <code>code</code></p>",
|
||||
"audioId": null,
|
||||
"feedback": "<p>That is not correct.</p>",
|
||||
},
|
||||
{
|
||||
"answer": "<p>Some <em>italics</em></p>
|
||||
<p>A second answer paragraph.</p>",
|
||||
"audioId": null,
|
||||
"feedback": null,
|
||||
},
|
||||
{
|
||||
"answer": "<p><code> code in </code> code tags</p>",
|
||||
"audioId": null,
|
||||
"feedback": null,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,15 +7,18 @@ exports[`add-video-question plugin > should match the video snapshot 1`] = `
|
||||
"answers": [
|
||||
{
|
||||
"answer": "<p>Some inline <code>code</code></p>",
|
||||
"audioId": null,
|
||||
"feedback": "<p>That is not correct.</p>",
|
||||
},
|
||||
{
|
||||
"answer": "<p>Some <em>italics</em></p>
|
||||
<p>A second answer paragraph.</p>",
|
||||
"audioId": null,
|
||||
"feedback": null,
|
||||
},
|
||||
{
|
||||
"answer": "<p><code> code in </code> code tags</p>",
|
||||
"audioId": null,
|
||||
"feedback": null,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -58,23 +58,56 @@ function getAnswers(answersNodes) {
|
||||
|
||||
return answerGroups.map(answerGroup => {
|
||||
const answerTree = root(answerGroup);
|
||||
const feedback = find(answerTree, { value: '--feedback--' });
|
||||
|
||||
if (feedback) {
|
||||
const answerNodes = getAllBefore(answerTree, '--feedback--');
|
||||
const feedbackNodes = getSection(answerTree, '--feedback--');
|
||||
const feedbackNodes = getSection(answerTree, '--feedback--');
|
||||
const audioIdNodes = getSection(answerTree, '--audio-id--');
|
||||
const hasFeedback = feedbackNodes.length > 0;
|
||||
const hasAudioId = audioIdNodes.length > 0;
|
||||
|
||||
if (hasFeedback || hasAudioId) {
|
||||
let answerNodes;
|
||||
|
||||
if (hasFeedback && hasAudioId) {
|
||||
const feedbackHeading = find(answerTree, {
|
||||
type: 'heading',
|
||||
children: [{ type: 'text', value: '--feedback--' }]
|
||||
});
|
||||
const audioIdHeading = find(answerTree, {
|
||||
type: 'heading',
|
||||
children: [{ type: 'text', value: '--audio-id--' }]
|
||||
});
|
||||
|
||||
const feedbackIndex = answerTree.children.indexOf(feedbackHeading);
|
||||
const audioIdIndex = answerTree.children.indexOf(audioIdHeading);
|
||||
const firstMarker =
|
||||
feedbackIndex < audioIdIndex ? '--feedback--' : '--audio-id--';
|
||||
answerNodes = getAllBefore(answerTree, firstMarker);
|
||||
} else if (hasFeedback) {
|
||||
answerNodes = getAllBefore(answerTree, '--feedback--');
|
||||
} else {
|
||||
answerNodes = getAllBefore(answerTree, '--audio-id--');
|
||||
}
|
||||
|
||||
if (answerNodes.length < 1) {
|
||||
throw Error('Answer missing');
|
||||
}
|
||||
|
||||
let extractedAudioId = null;
|
||||
if (hasAudioId) {
|
||||
const audioIdContent = getParagraphContent(audioIdNodes[0]);
|
||||
if (audioIdContent && audioIdContent.trim()) {
|
||||
extractedAudioId = audioIdContent.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
answer: mdastToHtml(answerNodes),
|
||||
feedback: mdastToHtml(feedbackNodes)
|
||||
feedback: hasFeedback ? mdastToHtml(feedbackNodes) : null,
|
||||
audioId: extractedAudioId
|
||||
};
|
||||
}
|
||||
|
||||
return { answer: mdastToHtml(answerGroup), feedback: null };
|
||||
return { answer: mdastToHtml(answerGroup), feedback: null, audioId: null };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@ import parseFixture from '../__fixtures__/parse-fixture';
|
||||
import addVideoQuestion from './add-video-question';
|
||||
|
||||
describe('add-video-question plugin', () => {
|
||||
let simpleAST, videoAST, multipleQuestionAST, videoOutOfOrderAST;
|
||||
let simpleAST,
|
||||
videoAST,
|
||||
multipleQuestionAST,
|
||||
videoOutOfOrderAST,
|
||||
videoWithAudioAST;
|
||||
const plugin = addVideoQuestion();
|
||||
let file = { data: {} };
|
||||
|
||||
@@ -16,6 +20,7 @@ describe('add-video-question plugin', () => {
|
||||
videoOutOfOrderAST = await parseFixture(
|
||||
'with-video-question-out-of-order.md'
|
||||
);
|
||||
videoWithAudioAST = await parseFixture('with-video-question-audio.md');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -43,6 +48,7 @@ describe('add-video-question plugin', () => {
|
||||
expect(question.answers[0]).toHaveProperty('answer');
|
||||
expect(question.answers[0].answer).toBeTruthy();
|
||||
expect(question.answers[0]).toHaveProperty('feedback');
|
||||
expect(question.answers[0]).toHaveProperty('audioId');
|
||||
};
|
||||
|
||||
it('should generate a questions array from a video challenge AST', () => {
|
||||
@@ -76,16 +82,19 @@ describe('add-video-question plugin', () => {
|
||||
expect(testObject.solution).toBe(3);
|
||||
expect(testObject.answers[0]).toStrictEqual({
|
||||
answer: '<p>Some inline <code>code</code></p>',
|
||||
feedback: '<p>That is not correct.</p>'
|
||||
feedback: '<p>That is not correct.</p>',
|
||||
audioId: null
|
||||
});
|
||||
expect(testObject.answers[1]).toStrictEqual({
|
||||
answer: `<p>Some <em>italics</em></p>
|
||||
<p>A second answer paragraph.</p>`,
|
||||
feedback: null
|
||||
feedback: null,
|
||||
audioId: null
|
||||
});
|
||||
expect(testObject.answers[2]).toStrictEqual({
|
||||
answer: '<p><code> code in </code> code tags</p>',
|
||||
feedback: null
|
||||
feedback: null,
|
||||
audioId: null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,6 +110,31 @@ describe('add-video-question plugin', () => {
|
||||
expect(() => plugin(simpleAST)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should extract audioId from answers when present', () => {
|
||||
plugin(videoWithAudioAST, file);
|
||||
|
||||
const testObject = file.data.questions[0];
|
||||
|
||||
expect(testObject.answers[0]).toStrictEqual({
|
||||
answer: '<p>Some inline <code>code</code></p>',
|
||||
feedback: '<p>That is not correct.</p>',
|
||||
audioId: 'answer1-audio'
|
||||
});
|
||||
|
||||
expect(testObject.answers[1]).toStrictEqual({
|
||||
answer: `<p>Some <em>italics</em></p>
|
||||
<p>A second answer paragraph.</p>`,
|
||||
feedback: null,
|
||||
audioId: 'answer2-audio'
|
||||
});
|
||||
|
||||
expect(testObject.answers[2]).toStrictEqual({
|
||||
answer: '<p><code> code in </code> code tags</p>',
|
||||
feedback: null,
|
||||
audioId: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should match the video snapshot', () => {
|
||||
plugin(videoAST, file);
|
||||
expect(file.data).toMatchSnapshot();
|
||||
|
||||
@@ -35,6 +35,7 @@ const VALID_MARKERS = [
|
||||
'## --before-user-code--',
|
||||
|
||||
// Level 3
|
||||
'### --audio-id--',
|
||||
'### --feedback--',
|
||||
'### --question--',
|
||||
|
||||
|
||||
Reference in New Issue
Block a user