diff --git a/client/src/templates/Challenges/fill-in-the-blank/parse-blanks.test.ts b/client/src/templates/Challenges/fill-in-the-blank/parse-blanks.test.ts new file mode 100644 index 00000000000..a0de29f6814 --- /dev/null +++ b/client/src/templates/Challenges/fill-in-the-blank/parse-blanks.test.ts @@ -0,0 +1,130 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { parseBlanks } from './parse-blanks'; + +describe('parseBlanks', () => { + it('handles strings without blanks', () => { + expect(parseBlanks('

hello world

')).toEqual([ + [{ type: 'text', value: 'hello world' }] + ]); + }); + + it('handles strings without blanks and with multiple paragraphs', () => { + expect(parseBlanks('

hello world

dlrow olleh

')).toEqual([ + [{ type: 'text', value: 'hello world' }], + [{ type: 'text', value: 'dlrow olleh' }] + ]); + }); + + it('handles strings with blanks', () => { + expect(parseBlanks('

hello _!

')).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 }, + { type: 'text', value: '!' } + ] + ]); + + expect(parseBlanks('

hello _! Nice _ to meet you.

')).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 }, + { type: 'text', value: '! Nice ' }, + { type: 'blank', value: 1 }, + { type: 'text', value: ' to meet you.' } + ] + ]); + }); + + it('handles paragraphs with a leading blank', () => { + expect(parseBlanks('

_ hello

')).toEqual([ + [ + { type: 'blank', value: 0 }, + { type: 'text', value: ' hello' } + ] + ]); + }); + + it('removes trailing empty strings', () => { + expect(parseBlanks('

hello _

')).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 } + ] + ]); + }); + + it('handles strings with blanks and multiple paragraphs', () => { + expect(parseBlanks(`

hello _!

dlrow _

`)).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 }, + { type: 'text', value: '!' } + ], + [ + { type: 'text', value: 'dlrow ' }, + { type: 'blank', value: 1 } + ] + ]); + }); + + it('ignores newlines between paragraphs', () => { + expect( + parseBlanks(`

hello _!

+ +

dlrow _

`) + ).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 }, + { type: 'text', value: '!' } + ], + [ + { type: 'text', value: 'dlrow ' }, + { type: 'blank', value: 1 } + ] + ]); + }); + + it('ignores whitespace surrounding the input', () => { + expect( + parseBlanks(`

hello _!

+ `) + ).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 }, + { type: 'text', value: '!' } + ] + ]); + }); + + it('counts blanks across multiple paragraphs', () => { + expect( + parseBlanks(`

hello _!

dlrow _ _

even _ more _

`) + ).toEqual([ + [ + { type: 'text', value: 'hello ' }, + { type: 'blank', value: 0 }, + { type: 'text', value: '!' } + ], + [ + { type: 'text', value: 'dlrow ' }, + { type: 'blank', value: 1 }, + { type: 'text', value: ' ' }, + { type: 'blank', value: 2 } + ], + [ + { type: 'text', value: ' even ' }, + { type: 'blank', value: 3 }, + { type: 'text', value: ' more ' }, + { type: 'blank', value: 4 } + ] + ]); + }); + + it('throws if the string is not wrapped in p tags', () => { + expect(() => parseBlanks('hello _!')).toThrow(); + expect(() => parseBlanks('

hello _!

hello _!')).toThrow(); + expect(() => parseBlanks('hello _!

hello

')).toThrow(); + }); +}); diff --git a/client/src/templates/Challenges/fill-in-the-blank/parse-blanks.ts b/client/src/templates/Challenges/fill-in-the-blank/parse-blanks.ts new file mode 100644 index 00000000000..84a018df0aa --- /dev/null +++ b/client/src/templates/Challenges/fill-in-the-blank/parse-blanks.ts @@ -0,0 +1,52 @@ +type TextNode = { type: 'text'; value: string }; +type BlankNode = { type: 'blank'; value: number }; +type ParagraphElement = TextNode | BlankNode; + +export const parseBlanks = (text: string) => { + const trimmed = text.trim(); + if (!trimmed.startsWith('

') || !trimmed.endsWith('

')) { + throw new Error(`Expected +${text} +to be wrapped in

tags`); + } + // We're expecting only paragraphs, so text after

and before

can be + // ignored. + const rawParagraphs = trimmed + .split('

') + .map(s => s.replace(/<\/p>\s*/, '')); + // There is always a leading empty string, so we remove it. + rawParagraphs.shift(); + + const { paragraphs } = rawParagraphs.reduce( + (acc, p) => { + const splitByBlank = p.split('_'); + + const parsedParagraph = splitByBlank + .map((text, i) => [ + { type: 'text', value: text }, + { type: 'blank', value: acc.count + i } + ]) + .flat(); + parsedParagraph.pop(); // remove last blank + + const paragraph = parsedParagraph.filter(p => { + // remove empty strings + if (p.type === 'text') { + return p.value; + } else { + return true; + } + }); + return { + count: acc.count + splitByBlank.length - 1, + paragraphs: [...acc.paragraphs, paragraph] + }; + }, + { count: 0, paragraphs: [] } as { + count: number; + paragraphs: ParagraphElement[][]; + } + ); + + return paragraphs; +}; diff --git a/client/src/templates/Challenges/fill-in-the-blank/show.tsx b/client/src/templates/Challenges/fill-in-the-blank/show.tsx index 2b839114d1d..de07301cc56 100644 --- a/client/src/templates/Challenges/fill-in-the-blank/show.tsx +++ b/client/src/templates/Challenges/fill-in-the-blank/show.tsx @@ -27,12 +27,13 @@ import { openModal, updateSolutionFormValues } from '../redux/actions'; +import Scene from '../components/scene/scene'; import { isChallengeCompletedSelector } from '../redux/selectors'; +import { parseBlanks } from './parse-blanks'; // Styles import '../video.css'; import './show.css'; -import Scene from '../components/scene/scene'; // Redux Setup const mapStateToProps = createSelector( @@ -273,8 +274,8 @@ class ShowFillInTheBlank extends Component< )} - ${title}`; const { allBlanksFilled, feedback, showFeedback, showWrong } = this.state; + const paragraphs = parseBlanks(sentence); - const splitSentence = sentence.replace(/^

|<\/p>$/g, '').split('_'); const blankAnswers = blanks.map(b => b.answer); return ( @@ -328,37 +329,37 @@ class ShowFillInTheBlank extends Component< if it encounters a key combination, so we have to pass in the individual keys to observe */}

-

- {splitSentence.map((s, i) => { - return ( - - - {blankAnswers[i] && ( - - )} - - ); - })} -

+ {paragraphs.map((p, i) => { + return ( + // both keys, i and j, are stable between renders, since + // the paragraphs are static. +

+ {p.map((node, j) => { + if (node.type === 'text') return node.value; + if (node.type === 'blank') + return ( + + ); + })} +

+ ); + })}
diff --git a/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-bad-paragraph.json b/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-bad-paragraph.json new file mode 100644 index 00000000000..9ad13e9d4c3 --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-bad-paragraph.json @@ -0,0 +1,291 @@ +{ + "type": "root", + "children": [ + { + "type": "heading", + "depth": 1, + "children": [ + { + "type": "text", + "value": "--description--", + "position": { + "start": { "line": 1, "column": 3, "offset": 2 }, + "end": { "line": 1, "column": 18, "offset": 17 } + } + } + ], + "position": { + "start": { "line": 1, "column": 1, "offset": 0 }, + "end": { "line": 1, "column": 18, "offset": 17 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "In English, to check or confirm something people sometimes use tag questions. For example, ", + "position": { + "start": { "line": 3, "column": 1, "offset": 19 }, + "end": { "line": 3, "column": 92, "offset": 110 } + } + }, + { + "type": "inlineCode", + "value": "You are a programmer, right?", + "position": { + "start": { "line": 3, "column": 92, "offset": 110 }, + "end": { "line": 3, "column": 122, "offset": 140 } + } + }, + { + "type": "text", + "value": " Here, ", + "position": { + "start": { "line": 3, "column": 122, "offset": 140 }, + "end": { "line": 3, "column": 129, "offset": 147 } + } + }, + { + "type": "inlineCode", + "value": "right?", + "position": { + "start": { "line": 3, "column": 129, "offset": 147 }, + "end": { "line": 3, "column": 137, "offset": 155 } + } + }, + { + "type": "text", + "value": " is used as a tag to check or confirm the previous statement.", + "position": { + "start": { "line": 3, "column": 137, "offset": 155 }, + "end": { "line": 3, "column": 198, "offset": 216 } + } + } + ], + "position": { + "start": { "line": 3, "column": 1, "offset": 19 }, + "end": { "line": 3, "column": 198, "offset": 216 } + } + }, + { + "type": "heading", + "depth": 1, + "children": [ + { + "type": "text", + "value": "--fillInTheBlank--", + "position": { + "start": { "line": 5, "column": 3, "offset": 220 }, + "end": { "line": 5, "column": 21, "offset": 238 } + } + } + ], + "position": { + "start": { "line": 5, "column": 1, "offset": 218 }, + "end": { "line": 5, "column": 21, "offset": 238 } + } + }, + { + "type": "heading", + "depth": 2, + "children": [ + { + "type": "text", + "value": "--sentence--", + "position": { + "start": { "line": 7, "column": 4, "offset": 243 }, + "end": { "line": 7, "column": 16, "offset": 255 } + } + } + ], + "position": { + "start": { "line": 7, "column": 1, "offset": 240 }, + "end": { "line": 7, "column": 16, "offset": 255 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "A sentence _ paragraph 1", + "position": { + "start": { "line": 9, "column": 1, "offset": 257 }, + "end": { "line": 9, "column": 27, "offset": 283 } + } + }, + { + "type": "text", + "value": "\n", + "position": { + "start": { "line": 9, "column": 27, "offset": 283 }, + "end": { "line": 10, "column": 1, "offset": 284 } + } + }, + { + "type": "inlineCode", + "value": "not enough newlines, so no paragraph 2", + "position": { + "start": { "line": 10, "column": 1, "offset": 284 }, + "end": { "line": 10, "column": 41, "offset": 324 } + } + } + ], + "position": { + "start": { "line": 9, "column": 1, "offset": 257 }, + "end": { "line": 10, "column": 41, "offset": 324 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sentence in _ 2", + "position": { + "start": { "line": 12, "column": 1, "offset": 326 }, + "end": { "line": 12, "column": 16, "offset": 341 } + } + } + ], + "position": { + "start": { "line": 12, "column": 1, "offset": 326 }, + "end": { "line": 12, "column": 16, "offset": 341 } + } + }, + { + "type": "heading", + "depth": 2, + "children": [ + { + "type": "text", + "value": "--blanks--", + "position": { + "start": { "line": 14, "column": 4, "offset": 346 }, + "end": { "line": 14, "column": 14, "offset": 356 } + } + } + ], + "position": { + "start": { "line": 14, "column": 1, "offset": 343 }, + "end": { "line": 14, "column": 14, "offset": 356 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "are", + "position": { + "start": { "line": 16, "column": 1, "offset": 358 }, + "end": { "line": 16, "column": 6, "offset": 363 } + } + } + ], + "position": { + "start": { "line": 16, "column": 1, "offset": 358 }, + "end": { "line": 16, "column": 6, "offset": 363 } + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "--feedback--", + "position": { + "start": { "line": 18, "column": 5, "offset": 369 }, + "end": { "line": 18, "column": 17, "offset": 381 } + } + } + ], + "position": { + "start": { "line": 18, "column": 1, "offset": 365 }, + "end": { "line": 18, "column": 17, "offset": 381 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pay attention to the verb in the sentence.", + "position": { + "start": { "line": 20, "column": 1, "offset": 383 }, + "end": { "line": 20, "column": 43, "offset": 425 } + } + } + ], + "position": { + "start": { "line": 20, "column": 1, "offset": 383 }, + "end": { "line": 20, "column": 43, "offset": 425 } + } + }, + { + "type": "thematicBreak", + "position": { + "start": { "line": 22, "column": 1, "offset": 427 }, + "end": { "line": 22, "column": 4, "offset": 430 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "right", + "position": { + "start": { "line": 24, "column": 1, "offset": 432 }, + "end": { "line": 24, "column": 8, "offset": 439 } + } + } + ], + "position": { + "start": { "line": 24, "column": 1, "offset": 432 }, + "end": { "line": 24, "column": 8, "offset": 439 } + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "--feedback--", + "position": { + "start": { "line": 26, "column": 5, "offset": 445 }, + "end": { "line": 26, "column": 17, "offset": 457 } + } + } + ], + "position": { + "start": { "line": 26, "column": 1, "offset": 441 }, + "end": { "line": 26, "column": 17, "offset": 457 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pay attention to the verb in the sentence.", + "position": { + "start": { "line": 28, "column": 1, "offset": 459 }, + "end": { "line": 28, "column": 43, "offset": 501 } + } + } + ], + "position": { + "start": { "line": 28, "column": 1, "offset": 459 }, + "end": { "line": 28, "column": 43, "offset": 501 } + } + } + ], + "position": { + "start": { "line": 1, "column": 1, "offset": 0 }, + "end": { "line": 29, "column": 1, "offset": 502 } + } +} diff --git a/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-bad-sentence.json b/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-bad-sentence.json new file mode 100644 index 00000000000..07837f4d70b --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-bad-sentence.json @@ -0,0 +1,275 @@ +{ + "type": "root", + "children": [ + { + "type": "heading", + "depth": 1, + "children": [ + { + "type": "text", + "value": "--description--", + "position": { + "start": { "line": 1, "column": 3, "offset": 2 }, + "end": { "line": 1, "column": 18, "offset": 17 } + } + } + ], + "position": { + "start": { "line": 1, "column": 1, "offset": 0 }, + "end": { "line": 1, "column": 18, "offset": 17 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "In English, to check or confirm something people sometimes use tag questions. For example, ", + "position": { + "start": { "line": 3, "column": 1, "offset": 19 }, + "end": { "line": 3, "column": 92, "offset": 110 } + } + }, + { + "type": "inlineCode", + "value": "You are a programmer, right?", + "position": { + "start": { "line": 3, "column": 92, "offset": 110 }, + "end": { "line": 3, "column": 122, "offset": 140 } + } + }, + { + "type": "text", + "value": " Here, ", + "position": { + "start": { "line": 3, "column": 122, "offset": 140 }, + "end": { "line": 3, "column": 129, "offset": 147 } + } + }, + { + "type": "inlineCode", + "value": "right?", + "position": { + "start": { "line": 3, "column": 129, "offset": 147 }, + "end": { "line": 3, "column": 137, "offset": 155 } + } + }, + { + "type": "text", + "value": " is used as a tag to check or confirm the previous statement.", + "position": { + "start": { "line": 3, "column": 137, "offset": 155 }, + "end": { "line": 3, "column": 198, "offset": 216 } + } + } + ], + "position": { + "start": { "line": 3, "column": 1, "offset": 19 }, + "end": { "line": 3, "column": 198, "offset": 216 } + } + }, + { + "type": "heading", + "depth": 1, + "children": [ + { + "type": "text", + "value": "--fillInTheBlank--", + "position": { + "start": { "line": 5, "column": 3, "offset": 220 }, + "end": { "line": 5, "column": 21, "offset": 238 } + } + } + ], + "position": { + "start": { "line": 5, "column": 1, "offset": 218 }, + "end": { "line": 5, "column": 21, "offset": 238 } + } + }, + { + "type": "heading", + "depth": 2, + "children": [ + { + "type": "text", + "value": "--sentence--", + "position": { + "start": { "line": 7, "column": 4, "offset": 243 }, + "end": { "line": 7, "column": 16, "offset": 255 } + } + } + ], + "position": { + "start": { "line": 7, "column": 1, "offset": 240 }, + "end": { "line": 7, "column": 16, "offset": 255 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "A sentence _ paragraph 1", + "position": { + "start": { "line": 9, "column": 1, "offset": 257 }, + "end": { "line": 9, "column": 27, "offset": 283 } + } + } + ], + "position": { + "start": { "line": 9, "column": 1, "offset": 257 }, + "end": { "line": 9, "column": 27, "offset": 283 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Sentence in _ 2", + "position": { + "start": { "line": 11, "column": 1, "offset": 285 }, + "end": { "line": 11, "column": 16, "offset": 300 } + } + } + ], + "position": { + "start": { "line": 11, "column": 1, "offset": 285 }, + "end": { "line": 11, "column": 16, "offset": 300 } + } + }, + { + "type": "heading", + "depth": 2, + "children": [ + { + "type": "text", + "value": "--blanks--", + "position": { + "start": { "line": 13, "column": 4, "offset": 305 }, + "end": { "line": 13, "column": 14, "offset": 315 } + } + } + ], + "position": { + "start": { "line": 13, "column": 1, "offset": 302 }, + "end": { "line": 13, "column": 14, "offset": 315 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "are", + "position": { + "start": { "line": 15, "column": 1, "offset": 317 }, + "end": { "line": 15, "column": 6, "offset": 322 } + } + } + ], + "position": { + "start": { "line": 15, "column": 1, "offset": 317 }, + "end": { "line": 15, "column": 6, "offset": 322 } + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "--feedback--", + "position": { + "start": { "line": 17, "column": 5, "offset": 328 }, + "end": { "line": 17, "column": 17, "offset": 340 } + } + } + ], + "position": { + "start": { "line": 17, "column": 1, "offset": 324 }, + "end": { "line": 17, "column": 17, "offset": 340 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pay attention to the verb in the sentence.", + "position": { + "start": { "line": 19, "column": 1, "offset": 342 }, + "end": { "line": 19, "column": 43, "offset": 384 } + } + } + ], + "position": { + "start": { "line": 19, "column": 1, "offset": 342 }, + "end": { "line": 19, "column": 43, "offset": 384 } + } + }, + { + "type": "thematicBreak", + "position": { + "start": { "line": 21, "column": 1, "offset": 386 }, + "end": { "line": 21, "column": 4, "offset": 389 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "right", + "position": { + "start": { "line": 23, "column": 1, "offset": 391 }, + "end": { "line": 23, "column": 8, "offset": 398 } + } + } + ], + "position": { + "start": { "line": 23, "column": 1, "offset": 391 }, + "end": { "line": 23, "column": 8, "offset": 398 } + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "--feedback--", + "position": { + "start": { "line": 25, "column": 5, "offset": 404 }, + "end": { "line": 25, "column": 17, "offset": 416 } + } + } + ], + "position": { + "start": { "line": 25, "column": 1, "offset": 400 }, + "end": { "line": 25, "column": 17, "offset": 416 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pay attention to the verb in the sentence.", + "position": { + "start": { "line": 27, "column": 1, "offset": 418 }, + "end": { "line": 27, "column": 43, "offset": 460 } + } + } + ], + "position": { + "start": { "line": 27, "column": 1, "offset": 418 }, + "end": { "line": 27, "column": 43, "offset": 460 } + } + } + ], + "position": { + "start": { "line": 1, "column": 1, "offset": 0 }, + "end": { "line": 28, "column": 1, "offset": 461 } + } +} diff --git a/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-two-sentences.json b/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-two-sentences.json new file mode 100644 index 00000000000..78438850820 --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/ast-fill-in-the-blank-two-sentences.json @@ -0,0 +1,275 @@ +{ + "type": "root", + "children": [ + { + "type": "heading", + "depth": 1, + "children": [ + { + "type": "text", + "value": "--description--", + "position": { + "start": { "line": 1, "column": 3, "offset": 2 }, + "end": { "line": 1, "column": 18, "offset": 17 } + } + } + ], + "position": { + "start": { "line": 1, "column": 1, "offset": 0 }, + "end": { "line": 1, "column": 18, "offset": 17 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "In English, to check or confirm something people sometimes use tag questions. For example, ", + "position": { + "start": { "line": 3, "column": 1, "offset": 19 }, + "end": { "line": 3, "column": 92, "offset": 110 } + } + }, + { + "type": "inlineCode", + "value": "You are a programmer, right?", + "position": { + "start": { "line": 3, "column": 92, "offset": 110 }, + "end": { "line": 3, "column": 122, "offset": 140 } + } + }, + { + "type": "text", + "value": " Here, ", + "position": { + "start": { "line": 3, "column": 122, "offset": 140 }, + "end": { "line": 3, "column": 129, "offset": 147 } + } + }, + { + "type": "inlineCode", + "value": "right?", + "position": { + "start": { "line": 3, "column": 129, "offset": 147 }, + "end": { "line": 3, "column": 137, "offset": 155 } + } + }, + { + "type": "text", + "value": " is used as a tag to check or confirm the previous statement.", + "position": { + "start": { "line": 3, "column": 137, "offset": 155 }, + "end": { "line": 3, "column": 198, "offset": 216 } + } + } + ], + "position": { + "start": { "line": 3, "column": 1, "offset": 19 }, + "end": { "line": 3, "column": 198, "offset": 216 } + } + }, + { + "type": "heading", + "depth": 1, + "children": [ + { + "type": "text", + "value": "--fillInTheBlank--", + "position": { + "start": { "line": 5, "column": 3, "offset": 220 }, + "end": { "line": 5, "column": 21, "offset": 238 } + } + } + ], + "position": { + "start": { "line": 5, "column": 1, "offset": 218 }, + "end": { "line": 5, "column": 21, "offset": 238 } + } + }, + { + "type": "heading", + "depth": 2, + "children": [ + { + "type": "text", + "value": "--sentence--", + "position": { + "start": { "line": 7, "column": 4, "offset": 243 }, + "end": { "line": 7, "column": 16, "offset": 255 } + } + } + ], + "position": { + "start": { "line": 7, "column": 1, "offset": 240 }, + "end": { "line": 7, "column": 16, "offset": 255 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "A sentence _ paragraph 1", + "position": { + "start": { "line": 9, "column": 1, "offset": 257 }, + "end": { "line": 9, "column": 27, "offset": 283 } + } + } + ], + "position": { + "start": { "line": 9, "column": 1, "offset": 257 }, + "end": { "line": 9, "column": 27, "offset": 283 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "Sentence in _ 2", + "position": { + "start": { "line": 11, "column": 1, "offset": 285 }, + "end": { "line": 11, "column": 18, "offset": 302 } + } + } + ], + "position": { + "start": { "line": 11, "column": 1, "offset": 285 }, + "end": { "line": 11, "column": 18, "offset": 302 } + } + }, + { + "type": "heading", + "depth": 2, + "children": [ + { + "type": "text", + "value": "--blanks--", + "position": { + "start": { "line": 13, "column": 4, "offset": 307 }, + "end": { "line": 13, "column": 14, "offset": 317 } + } + } + ], + "position": { + "start": { "line": 13, "column": 1, "offset": 304 }, + "end": { "line": 13, "column": 14, "offset": 317 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "are", + "position": { + "start": { "line": 15, "column": 1, "offset": 319 }, + "end": { "line": 15, "column": 6, "offset": 324 } + } + } + ], + "position": { + "start": { "line": 15, "column": 1, "offset": 319 }, + "end": { "line": 15, "column": 6, "offset": 324 } + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "--feedback--", + "position": { + "start": { "line": 17, "column": 5, "offset": 330 }, + "end": { "line": 17, "column": 17, "offset": 342 } + } + } + ], + "position": { + "start": { "line": 17, "column": 1, "offset": 326 }, + "end": { "line": 17, "column": 17, "offset": 342 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pay attention to the verb in the sentence.", + "position": { + "start": { "line": 19, "column": 1, "offset": 344 }, + "end": { "line": 19, "column": 43, "offset": 386 } + } + } + ], + "position": { + "start": { "line": 19, "column": 1, "offset": 344 }, + "end": { "line": 19, "column": 43, "offset": 386 } + } + }, + { + "type": "thematicBreak", + "position": { + "start": { "line": 21, "column": 1, "offset": 388 }, + "end": { "line": 21, "column": 4, "offset": 391 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "right", + "position": { + "start": { "line": 23, "column": 1, "offset": 393 }, + "end": { "line": 23, "column": 8, "offset": 400 } + } + } + ], + "position": { + "start": { "line": 23, "column": 1, "offset": 393 }, + "end": { "line": 23, "column": 8, "offset": 400 } + } + }, + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "--feedback--", + "position": { + "start": { "line": 25, "column": 5, "offset": 406 }, + "end": { "line": 25, "column": 17, "offset": 418 } + } + } + ], + "position": { + "start": { "line": 25, "column": 1, "offset": 402 }, + "end": { "line": 25, "column": 17, "offset": 418 } + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Pay attention to the verb in the sentence.", + "position": { + "start": { "line": 27, "column": 1, "offset": 420 }, + "end": { "line": 27, "column": 43, "offset": 462 } + } + } + ], + "position": { + "start": { "line": 27, "column": 1, "offset": 420 }, + "end": { "line": 27, "column": 43, "offset": 462 } + } + } + ], + "position": { + "start": { "line": 1, "column": 1, "offset": 0 }, + "end": { "line": 28, "column": 1, "offset": 463 } + } +} diff --git a/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-bad-paragraph.md b/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-bad-paragraph.md new file mode 100644 index 00000000000..141878a9b61 --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-bad-paragraph.md @@ -0,0 +1,28 @@ +# --description-- + +In English, to check or confirm something people sometimes use tag questions. For example, `You are a programmer, right?` Here, `right?` is used as a tag to check or confirm the previous statement. + +# --fillInTheBlank-- + +## --sentence-- + +`A sentence _ paragraph 1` +`not enough newlines, so no paragraph 2` + +Sentence in _ 2 + +## --blanks-- + +`are` + +### --feedback-- + +Pay attention to the verb in the sentence. + +--- + +`right` + +### --feedback-- + +Pay attention to the verb in the sentence. diff --git a/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-bad-sentence.md b/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-bad-sentence.md new file mode 100644 index 00000000000..db12c2e27b3 --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-bad-sentence.md @@ -0,0 +1,27 @@ +# --description-- + +In English, to check or confirm something people sometimes use tag questions. For example, `You are a programmer, right?` Here, `right?` is used as a tag to check or confirm the previous statement. + +# --fillInTheBlank-- + +## --sentence-- + +`A sentence _ paragraph 1` + +Sentence in _ 2 + +## --blanks-- + +`are` + +### --feedback-- + +Pay attention to the verb in the sentence. + +--- + +`right` + +### --feedback-- + +Pay attention to the verb in the sentence. diff --git a/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-two-sentences.md b/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-two-sentences.md new file mode 100644 index 00000000000..8732b5ab9ab --- /dev/null +++ b/tools/challenge-parser/parser/__fixtures__/with-fill-in-the-blank-two-sentences.md @@ -0,0 +1,27 @@ +# --description-- + +In English, to check or confirm something people sometimes use tag questions. For example, `You are a programmer, right?` Here, `right?` is used as a tag to check or confirm the previous statement. + +# --fillInTheBlank-- + +## --sentence-- + +`A sentence _ paragraph 1` + +`Sentence in _ 2` + +## --blanks-- + +`are` + +### --feedback-- + +Pay attention to the verb in the sentence. + +--- + +`right` + +### --feedback-- + +Pay attention to the verb in the sentence. diff --git a/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js b/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js index b15cdaa055a..e3be3f0e9cd 100644 --- a/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js +++ b/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js @@ -6,6 +6,32 @@ const mdastToHtml = require('./utils/mdast-to-html'); const { splitOnThematicBreak } = require('./utils/split-on-thematic-break'); +const NOT_IN_PARAGRAPHS = `Each inline code block in the fillInTheBlank sentence section must in its own paragraph +If you have more than one code block, check that they're separated by a blank line +Example of bad formatting: +\`too close\` +\`to each other\` + +Example of good formatting: +\`separated\` + +\`by a blank line\` + +`; + +const NOT_IN_CODE_BLOCK = `Each paragraph in the fillInTheBlank sentence section must be inside an inline code block +Example of bad formatting: +## --sentence-- + +This is a sentence + +Example of good formatting: +## --sentence-- + +\`This is a sentence\` + +`; + function plugin() { return transformer; function transformer(tree, file) { @@ -24,7 +50,17 @@ function plugin() { } function getfillInTheBlank(sentenceNodes, blanksNodes) { - const sentence = mdastToHtml(sentenceNodes); + const sentenceWithoutCodeBlocks = sentenceNodes.map(node => { + node.children.forEach(child => { + if (child.type === 'text' && child.value.trim() === '') + throw Error(NOT_IN_PARAGRAPHS); + if (child.type !== 'inlineCode') throw Error(NOT_IN_CODE_BLOCK); + }); + + const children = node.children.map(child => ({ ...child, type: 'text' })); + return { ...node, children }; + }); + const sentence = mdastToHtml(sentenceWithoutCodeBlocks); const blanks = getBlanks(blanksNodes); if (!sentence) throw Error('sentence is missing from fill in the blank'); diff --git a/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.test.js b/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.test.js index f7a62496015..3a362cf6a34 100644 --- a/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.test.js +++ b/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.test.js @@ -1,5 +1,8 @@ const mockFillInTheBlankAST = require('../__fixtures__/ast-fill-in-the-blank.json'); const mockFillInTheBlankYouAreAST = require('../__fixtures__/ast-fill-in-the-blank-one-blank.json'); +const mockFillInTheBlankTwoSentencesAST = require('../__fixtures__/ast-fill-in-the-blank-two-sentences.json'); +const mockFillInTheBlankBadSentence = require('../__fixtures__/ast-fill-in-the-blank-bad-sentence.json'); +const mockFillInTheBlankBadParagraph = require('../__fixtures__/ast-fill-in-the-blank-bad-paragraph.json'); const addFillInTheBlankQuestion = require('./add-fill-in-the-blank'); describe('fill-in-the-blanks plugin', () => { @@ -66,6 +69,63 @@ describe('fill-in-the-blanks plugin', () => { }); }); + it('should extract the sentence from the surrounding inline code block', () => { + plugin(mockFillInTheBlankAST, file); + const testObject = file.data.fillInTheBlank; + + expect(testObject.sentence).toBe( + '

Hello, You _ the new graphic designer, _? _ to meet you!

' + ); + }); + + it('should extract sentences from multiple inline code blocks', () => { + plugin(mockFillInTheBlankTwoSentencesAST, file); + const testObject = file.data.fillInTheBlank; + + expect(testObject.sentence).toBe( + `

A sentence _ paragraph 1

+

Sentence in _ 2

` + ); + }); + + it('should throw if a sentence is not inside an inline code block', () => { + expect(() => { + plugin(mockFillInTheBlankBadSentence, file); + }).toThrow( + `Each paragraph in the fillInTheBlank sentence section must be inside an inline code block +Example of bad formatting: +## --sentence-- + +This is a sentence + +Example of good formatting: +## --sentence-- + +\`This is a sentence\` + +` + ); + }); + + it('should throw if there are multiple inline code blocks in the same paragraph', () => { + expect(() => { + plugin(mockFillInTheBlankBadParagraph, file); + }).toThrow( + `Each inline code block in the fillInTheBlank sentence section must in its own paragraph +If you have more than one code block, check that they're separated by a blank line +Example of bad formatting: +\`too close\` +\`to each other\` + +Example of good formatting: +\`separated\` + +\`by a blank line\` + +` + ); + }); + it('should handle one blank', () => { plugin(mockFillInTheBlankYouAreAST, file); const testObject = file.data.fillInTheBlank;