chore: import upstream snapshot with attribution
CD - Docker - GHCR Images / Build and Push Images (push) Waiting to run
i18n - Build Validation / Validate i18n Builds (24) (push) Has been cancelled
CI - Node.js / Lint (24) (push) Has been cancelled
CI - Node.js / Build (24) (push) Has been cancelled
CI - Node.js / Test (24) (push) Has been cancelled
CI - Node.js / Test - Upcoming Changes (24) (push) Has been cancelled
CI - Node.js / Test - i18n (italian, 24) (push) Has been cancelled
CI - Node.js / Test - i18n (portuguese, 24) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 11:55:53 +08:00
commit dde272c4b8
19405 changed files with 2730632 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<code> code in </code> code tags _emphasis_ followed by <div><span>some nested html </span></div>
@@ -0,0 +1,3 @@
Paragraph 1
Third _hint_ with <code>code</code> and `inline code`
@@ -0,0 +1,13 @@
const { join } = require('path');
const remark = require('remark');
const directive = require('remark-directive');
const frontmatter = require('remark-frontmatter');
const { read } = require('to-vfile');
const parseFixture = async (filename) => remark()
.use(directive)
.use(frontmatter, ['yaml'])
.parse(await read(join(__dirname, filename)));
module.exports = parseFixture;
@@ -0,0 +1,176 @@
# --description--
When you add a lower rank heading element to the page, it's implied that you're starting a new subsection.
After the last <code>h2</code> element of the second `section` element, add an `h3` element with the text `Things cats love:`.
<blockquote>
<p>Some text in a blockquote</p>
<p>
Some text in a blockquote, with <code>code</code>
</p>
</blockquote>
# --instructions--
Do something with the `code`.
To test that adjacent tags are handled correctly:
a bit of <code>code</code> <tag>with more after a space</tag> and another pair of <strong>elements</strong> <em>with a space</em>
# --hints--
The second `section` element appears to be missing or does not have both an opening and closing tag.
```js
assert(
document.querySelectorAll('main > section')[1] &&
code.match(/\<\/section>/g).length == 2
);
```
There should be an `h3` element right above the second `section` element's closing tag.
```js
assert(
document.querySelectorAll('main > section')[1].lastElementChild.nodeName ===
'H3'
);
```
The `h3` element right above the second `section` element's closing tag should have the text `Things cats love:`. Make sure to include the colon at the end of the text.
```js
assert(
document
.querySelectorAll('main > section')[1]
.lastElementChild.innerText.toLowerCase()
.replace(/\s+/g, ' ') === 'things cats love:'
);
```
There should be an `h2` element with the text `Cat Lists` above the last `h3` element that is nested in the last `section` element'. You may have accidentally deleted the `h2` element.
```js
const secondSectionLastElemNode = document.querySelectorAll('main > section')[1]
.lastElementChild;
assert(
secondSectionLastElemNode.nodeName === 'H3' &&
secondSectionLastElemNode.previousElementSibling.innerText
.toLowerCase()
.replace(/\s+/g, ' ') === 'cat lists'
);
```
# --seed--
## --seed-contents--
::id{#html-key}
```html
<html>
<body>
<h1>CatPhotoApp</h1>
<main>
<section>
<h2>Cat Photos</h2>
<!-- TODO: Add link to cat photos -->
<p>
Click here to view more
<a target="_blank" href="https://www.freecodecamp.org/cat-photos"
>cat photos</a
>.
</p>
<a href="https://www.freecodecamp.org/cat-photos"
><img
src="https://bit.ly/fcc-relaxing-cat"
alt="A cute orange cat lying on its back."
/></a>
</section>
--fcc-editable-region--
<section>
<h2>Cat Lists</h2>
</section>
--fcc-editable-region--
</main>
</body>
</html>
```
```css
body {
background: white;
}
h1 {
font-size: 20px;
}
--fcc-editable-region--
--fcc-editable-region--
a {
color: green;
}
```
::id{#final-key}
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
<h1>CatPhotoApp</h1>
<main>
<section>
<h2>Cat Photos</h2>
<!-- TODO: Add link to cat photos -->
<p>
Click here to view more
<a target="_blank" href="https://www.freecodecamp.org/cat-photos"
>cat photos</a
>.
</p>
<a href="https://www.freecodecamp.org/cat-photos"
><img
src="https://bit.ly/fcc-relaxing-cat"
alt="A cute orange cat lying on its back."
/></a>
</section>
<section>
<h2>Cat Lists</h2>
</section>
</main>
</body>
</html>
```
```css
body {
background: white;
}
h1 {
font-size: 20px;
}
a {
color: green;
}
```
::id{#final-key}
```js
var x = 'y';
```
@@ -0,0 +1,47 @@
# --description--
This challenge has a scene.
# --scene--
```json
{
"setup": {
"background": "company2-center.png",
"characters": [
{
"character": "Maria",
"position": { "x": 50, "y": 0, "z": 1.5 },
"opacity": 0
}
],
"audio": {
"filename": "1.1-1.mp3",
"startTime": 1,
"startTimestamp": 2.6,
"finishTimestamp": 4
}
},
"commands": [
{
"character": "Maria",
"opacity": 1,
"startTime": 0
},
{
"character": "Maria",
"startTime": 0.7,
"finishTime": 2.4,
"dialogue": {
"text": "I'm Maria, the team lead.",
"align": "center"
}
},
{
"character": "Maria",
"opacity": 0,
"startTime": 3.4
}
]
}
```
@@ -0,0 +1,5 @@
```css
div {
background: red
}
```
@@ -0,0 +1,6 @@
```js
for (let index = 0; index < array.length; index++) {
const element = array[index];
// imported from script.md
}
```
@@ -0,0 +1,89 @@
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
```json
{
"compilerOptions": {
"target": "ES2020"
}
}
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --after-all--
```js
// after all code
function teardown() {
return 'cleaned up';
}
teardown();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --after-each--
```js
// after each code
function cleanup() {
return 'cleaned up';
}
cleanup();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,34 @@
# --description--
Paragraph 1
```html
code example
```
# --after-all--
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,34 @@
# --description--
Paragraph 1
```html
code example
```
# --after-each--
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,34 @@
# --description--
Paragraph 1
```html
code example
```
# --before-each--
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,34 @@
# --description--
Paragraph 1
```html
code example
```
# --before-all--
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --before-each--
```js
// before each code
function setup() {
return 'initialized';
}
setup();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --before-all--
```js
// before all code
function foo() {
return 'bar';
}
foo();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,73 @@
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
```js
// test code
```
First hint
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
# --seed--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,42 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```c
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,9 @@
# --fillInTheBlank--
## --sentence--
`BLANK BLANK`
## --blanks--
`你 (nǐ)`
@@ -0,0 +1,17 @@
# --fillInTheBlank--
## --sentence--
`我 (wǒ) BLANK UI 设计师 (shè jì shī) 。`
## --blanks--
`是 (shì)`
### --feedback--
Feedback text.
# --explanation--
Explanation text.
@@ -0,0 +1,9 @@
# --fillInTheBlank--
## --sentence--
`你好 (nǐ hǎo)`
## --blanks--
`你`
@@ -0,0 +1,9 @@
# --fillInTheBlank--
## --sentence--
`BLANK hǎo`
## --blanks--
`nǐ`
@@ -0,0 +1,9 @@
# --fillInTheBlank--
## --sentence--
`BLANK好`
## --blanks--
`你 (nǐ)`
@@ -0,0 +1,13 @@
# --fillInTheBlank--
## --sentence--
`BLANK 好 (hǎo) BLANK`
## --blanks--
`你`
---
`nǐ`
@@ -0,0 +1,46 @@
---
lang: zh-CN
inputType: pinyin-to-hanzi
---
# --fillInTheBlank--
## --sentence--
`BLANK BLANKBLANK 是王华 (shì Wang Hua),请问你 (qǐng wèn nǐ) BLANK 什么名字 (shén me míng zi)`
## --blanks--
`你 (nǐ)`
### --feedback--
Feedback text containing `汉字 (hàn zì)`.
---
`好 (hǎo)`
### --feedback--
This means "good" or "well".
---
`我 (wǒ)`
### --feedback--
This means "I".
---
`叫 (jiào)`
### --feedback--
This means "to be called".
# --explanation--
Explanation text containing `汉字 (hàn zì)`.
@@ -0,0 +1,54 @@
---
id: test-with-chinese-mcq
title: Ruby Test
challengeType: 19
lang: zh-CN
---
# --instructions--
Instructions containing `汉字 (hàn zì)`.
# --questions--
## --text--
Question text containing `汉字 (hàn zì)`.
## --answers--
`你好 (nǐ hǎo)`
### --feedback--
Feedback text.
---
`请 (qǐng)`
### --feedback--
`请 (qǐng)` is not correct.
---
`请问 (qǐng wèn)`
---
`问 (wèn)`
### --feedback--
Feedback text.
## --video-solution--
3
# --explanation--
`我是 (wǒ shì) Web 开发者 (kāi fā zhě)。` I am a web developer.
`你好 (nǐ hǎo),我是王华 (wǒ shì Wang Hua),请问你叫什么名字 (qǐng wèn nǐ jiào shén me míng zi)` Hello, I am Wang Hua, may I ask what your name is?
@@ -0,0 +1,87 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Quiz 1, question 1 with `中文 (zhōng wén)`
#### --audio--
```json
{
"audio": {
"filename": "7.3-1.mp3",
"startTimestamp": 24.7,
"finishTimestamp": 26.2
},
"transcript": [
{
"character": "Wang Hua",
"text": "你好 (nǐ hǎo)。"
}
]
}
```
#### --distractors--
Quiz 1, question 1, distractor 1 with `中文 (zhōng wén)`
---
Quiz 1, question 1, distractor 2 with `中文 (zhōng wén)`
---
Quiz 1, question 1, distractor 3 with `中文 (zhōng wén)`
#### --answer--
Quiz 1, question 1, answer with `中文 (zhōng wén)`
### --question--
#### --text--
Quiz 1, question 2 with `中文`
#### --distractors--
Quiz 1, question 2, distractor 1 with `中文`
---
Quiz 1, question 2, distractor 2 with `中文`
---
Quiz 1, question 2, distractor 3 with `中文`
#### --answer--
Quiz 1, question 2, answer with `中文`
### --question--
#### --text--
Quiz 1, question 3 with `zhōng wén`
#### --distractors--
Quiz 1, question 3, distractor 1 with `zhōng wén`
---
Quiz 1, question 3, distractor 2 with `zhōng wén`
---
Quiz 1, question 3, distractor 3 with `zhōng wén`
#### --answer--
Quiz 1, question 3, answer with `zhōng wén`
@@ -0,0 +1,35 @@
# --description--
This challenge has a Chinese scene with plain hanzi (no pinyin).
# --scene--
```json
{
"setup": {
"background": "company1-reception.png",
"characters": [
{
"character": "Wang Hua",
"position": { "x": 50, "y": 15, "z": 1.4 },
"opacity": 0
}
],
"audio": {
"filename": "test.mp3",
"startTime": 1
}
},
"commands": [
{
"character": "Wang Hua",
"startTime": 1,
"finishTime": 2,
"dialogue": {
"text": "你好,世界。",
"align": "center"
}
}
]
}
```
@@ -0,0 +1,37 @@
# --description--
This challenge has a Chinese scene with hanzi-pinyin pairs.
# --scene--
```json
{
"setup": {
"background": "company1-reception.png",
"characters": [
{
"character": "Wang Hua",
"position": { "x": 50, "y": 15, "z": 1.4 },
"opacity": 0
}
],
"audio": {
"filename": "ZH_A1_welcome_hello_world.mp3",
"startTime": 1,
"startTimestamp": 5.18,
"finishTimestamp": 6.71
}
},
"commands": [
{
"character": "Wang Hua",
"startTime": 1,
"finishTime": 2.53,
"dialogue": {
"text": "你好 (nǐ hǎo),世界 (shì jiè)。",
"align": "center"
}
}
]
}
```
@@ -0,0 +1,17 @@
# --description--
:root appears, :import appears
the next paragraph should appear
::import
even though it's an import directive, but if we use the full syntax `::directive-name{attr="name" attr2="a/path"}`
::import{component="name" from="script.md"}
it goes.
::: name [inline-content] {key=val}
a container directive
:::
@@ -0,0 +1,84 @@
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
--fcc-editable-region-- --fcc-editable-region--
background: green;
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,84 @@
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
--fcc-editable-region--
background: green;
--fcc-editable-region--
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,64 @@
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
# --seed--
This section intentionally has no `## --seed-contents--`.
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,7 @@
# --description--
This is a test file for empty interactive elements.
# --interactive--
no subsections
@@ -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`.
@@ -0,0 +1,44 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
--fcc-editable-region--
background: white;
--fcc-editable-region--
}
```
```js
var x = 'y';
```
@@ -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`.
@@ -0,0 +1,83 @@
# --description--
# this should still be inside --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -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 BLANK paragraph 1`
`not enough newlines, so no paragraph 2`
Sentence in BLANK 2
## --blanks--
`are`
### --feedback--
Pay attention to the verb in the sentence.
---
`right`
### --feedback--
Pay attention to the verb in the sentence.
@@ -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 BLANK paragraph 1`
Sentence in BLANK 2
## --blanks--
`are`
### --feedback--
Pay attention to the verb in the sentence.
---
`right`
### --feedback--
Pay attention to the verb in the sentence.
@@ -0,0 +1,29 @@
# --fillInTheBlank--
## --sentence--
`Sure thing! BLANK _ _.`
## --blanks--
`I'd`
### --feedback--
Feedback 1
## --blanks--
`love`
### --feedback--
Feedback 2
## --blanks--
`to`
### --feedback--
Feedback 3
@@ -0,0 +1,19 @@
# --description--
In English, when making introductions or identifying someone, you use the verb `to be`. In this case, `You are` is used to address the person Maria is talking to and affirmatively identify their occupation.
Maria is introducing herself and confirming Tom's job role. `Are` is used in the present affirmative to make a statement.
# --fillInTheBlank--
## --sentence--
`Hello, You BLANK the new graphic designer, right?`
## --blanks--
`are`
### --feedback--
The verb `to be` is an irregular verb. When conjugated with the pronoun `you`, `be` becomes `are`. For example: `You are an English learner.`
@@ -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 BLANK paragraph 1`
`Sentence in BLANK 2`
## --blanks--
`are`
### --feedback--
Pay attention to the verb in the sentence.
---
`right`
### --feedback--
Pay attention to the verb in the sentence.
@@ -0,0 +1,31 @@
# --description--
In English, when making introductions or identifying someone, you use the verb `to be`. In this case, `You are` is used to address the person Maria is talking to and affirmatively identify their occupation.
Maria is introducing herself and confirming Tom's job role. `Are` is used in the present affirmative to make a statement.
# --fillInTheBlank--
## --sentence--
`Hello, You BLANK the new graphic designer, BLANK? BLANK to meet you!`
## --blanks--
`are`
### --feedback--
The verb `to be` is an irregular verb. When conjugated with the pronoun `you`, `be` becomes `are`. For example: `You are an English learner.`
---
`right`
### --feedback--
Feedback 2
---
`Nice`
@@ -0,0 +1,51 @@
---
id: bd7123c8c441eddfaeb5bdef
title: Say Hello to HTML Elements
challengeType: 0
isHidden: false
videoUrl: 'https://scrimba.com/p/pVMPUv/cE8Gpt2'
forumTopicId: 18276
---
# --description--
Paragraph 1
```html
code example
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,86 @@
# --description--
Paragraph 1 ~~Strikethrough text~~. https://should.not.be.autolinked
```html
code example
```
| example | of a |
| --- | --- |
| gfm | table |
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,5 @@
::id{#html-key}
![test image](https://example.com/image.png)
Paragraph with an image ![test image](https://example.com/image.png) in it.
@@ -0,0 +1,61 @@
::import{component="Script" from="./script.md" }
::import{component="Second" from="./script-two.md" }
::import
# --description--
Paragraph 1 ::import
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
::use{component="Second"}
::id{#custom-name}
```js
var x = 'y';
```
::use{component="Script"}
@@ -0,0 +1,60 @@
::import{component="Script" from="./script.md" }
::import{component="Second" from="./script-two.md" }
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
::use{component="Second"}
::id{#custom-name}
```js
var x = 'y';
```
::use{component="Script"}
@@ -0,0 +1,57 @@
::import{component="Script" from="./script.md" }
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
::id{#custom-name}
```js
var x = 'y';
```
::use{component="Script"}
@@ -0,0 +1,17 @@
# --interactive--
Normal markdown
```html
<div>This is NOT an interactive element</div>
```
:::interactive_editor
```js
console.log('Interactive JS');
```
non-code md is not allowed
:::
@@ -0,0 +1,43 @@
# --interactive--
Normal markdown
```html
<div>This is NOT an interactive element</div>
```
:::interactive_editor
```js
console.log('Interactive JS');
```
:::
:::interactive_editor
```html
<div>This is an interactive element</div>
```
:::
```html
<div>This is not interactive</div>
```
:::interactive_editor
```html
<div>This is an interactive element</div>
```
```js
console.log('Interactive JS');
```
:::
```html
<div>This is also not interactive</div>
```
@@ -0,0 +1,42 @@
# --description--
Paragraph 1
```html
code example
```
# --after-all--
```js
// after all code
function teardown() {
return 'cleaned up';
}
teardown();
```
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,42 @@
# --description--
Paragraph 1
```html
code example
```
# --after-each--
```js
// after each code
function cleanup() {
return 'cleaned up';
}
cleanup();
```
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,42 @@
# --description--
Paragraph 1
```html
code example
```
# --before-each--
```js
// before each code
function setup() {
return 'initialized';
}
setup();
```
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,42 @@
# --description--
Paragraph 1
```html
code example
```
# --before-all--
```js
// before all code
function foo() {
return 'bar';
}
foo();
```
gubbins
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,57 @@
::import{component="Script" from="./script-with-markers.md" }
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
::id{#custom-name}
```js
var x = 'y';
```
::use{component="Script"}
@@ -0,0 +1,5 @@
# --hints--
First hint
Whoops, this should have been a test code block
@@ -0,0 +1,15 @@
# --interactive--
Testing multiple JavaScript files with unique filekeys.
:::interactive_editor
```js
console.log('First JavaScript file');
```
```js
console.log('Second JavaScript file');
```
:::
@@ -0,0 +1,85 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
# --solutions--
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
---
::id{#html-key}
```html
<html>
<body>
solution number two
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y';
```
---
::id{#html-key}
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: white;
}
```
```js
var x = 'y3';
```
@@ -0,0 +1,77 @@
# --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> code in </code> code tags
## --video-solution--
3
## --text--
Question 2
```js
var anything = 'y';
```
## --answers--
Some inline `code` again
### --feedback--
That is not correct.
---
Some *italics*
A second answer paragraph.
---
<code> code in </code> code tags
## --video-solution--
2
@@ -0,0 +1,33 @@
# --description--
This is the main description.
# --instructions--
These are the main instructions at depth 1.
```html
<div>Main instructions code</div>
```
# --something-else--
## --instructions--
These are nested instructions at depth 2 that should be ignored.
```html
<div>Nested instructions code</div>
```
### --instructions--
These are nested instructions at depth 3 that should also be ignored.
# --hints--
First hint
```js
// test code
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --after-all--
```ts
// after all code
function teardown() {
return 'cleaned up';
}
teardown();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --after-each--
```ts
// after each code
function cleanup() {
return 'cleaned up';
}
cleanup();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --before-each--
```ts
// before each code
function setup() {
return 'initialized';
}
setup();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,40 @@
# --description--
Paragraph 1
```html
code example
```
# --before-all--
```ts
// before all code
function foo() {
return 'bar';
}
foo();
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
Third *hint* with <code>code</code> and `inline code`
```js
// more test code
if(let x of xs) {
console.log(x);
}
```
@@ -0,0 +1,36 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio but empty transcript
#### --audio--
```json
{
"audio": {
"filename": "audio-with-empty-transcript.mp3"
},
"transcript": []
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,42 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with invalid JSON audio
#### --audio--
```json
{
"audio": {
"filename": "audio.mp3"
},
"transcript": [
{
"character": "A",
"text": "Hello"
}
]
// missing comma
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,39 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio missing filename
#### --audio--
```json
{
"audio": {},
"transcript": [
{
"character": "A",
"text": "Hello"
}
]
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,35 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio but no transcript
#### --audio--
```json
{
"audio": {
"filename": "audio-without-transcript.mp3"
}
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,29 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio not in JSON code block
#### --audio--
Some plain text audio data
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,40 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio transcript line missing character
#### --audio--
```json
{
"audio": {
"filename": "audio.mp3"
},
"transcript": [
{
"text": "Hello"
}
]
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,40 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio transcript line missing text
#### --audio--
```json
{
"audio": {
"filename": "audio.mp3"
},
"transcript": [
{
"character": "A"
}
]
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,36 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Question with audio transcript not array
#### --audio--
```json
{
"audio": {
"filename": "audio.mp3"
},
"transcript": "not an array"
}
```
#### --distractors--
Distractor 1
---
Distractor 2
---
Distractor 3
#### --answer--
Answer
@@ -0,0 +1,107 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Quiz 1, question 1 with audio timestamps
#### --audio--
```json
{
"audio": {
"filename": "audio-with-timestamps.mp3",
"startTimestamp": 1.5,
"finishTimestamp": 3.8
},
"transcript": [
{
"character": "Maria",
"text": "Hello, how are you?"
},
{
"character": "Tom",
"text": "I'm doing well, thank you."
}
]
}
```
#### --distractors--
Quiz 1, question 1, distractor 1
---
Quiz 1, question 1, distractor 2
---
Quiz 1, question 1, distractor 3
#### --answer--
Quiz 1, question 1, answer
### --question--
#### --text--
Quiz 1, question 2 with audio but no timestamps
#### --audio--
```json
{
"audio": {
"filename": "audio-full-file.mp3"
},
"transcript": [
{
"character": "Speaker",
"text": "This is the full audio transcript."
}
]
}
```
#### --distractors--
Quiz 1, question 2, distractor 1
---
Quiz 1, question 2, distractor 2
---
Quiz 1, question 2, distractor 3
#### --answer--
Quiz 1, question 2, answer
### --question--
#### --text--
Quiz 1, question 3 without audio
#### --distractors--
Quiz 1, question 3, distractor 1
---
Quiz 1, question 3, distractor 2
---
Quiz 1, question 3, distractor 3
#### --answer--
Quiz 1, question 3, answer
@@ -0,0 +1,93 @@
# --quizzes--
## --quiz--
### --question--
#### --text--
Quiz 1, question 1
#### --distractors--
Quiz 1, question 1, distractor 1
---
Quiz 1, question 1, distractor 2
---
Quiz 1, question 1, distractor 3
#### --answer--
Quiz 1, question 1, answer
### --question--
#### --text--
Quiz 1, question 2
#### --distractors--
Quiz 1, question 2, distractor 1
---
Quiz 1, question 2, distractor 2
---
Quiz 1, question 2, distractor 3
#### --answer--
Quiz 1, question 2, answer
## --quiz--
### --question--
#### --text--
Quiz 2, question 1
#### --distractors--
Quiz 2, question 1, distractor 1
---
Quiz 2, question 1, distractor 2
---
Quiz 2, question 1, distractor 3
#### --answer--
Quiz 2, question 1, answer
### --question--
#### --text--
Quiz 2, question 2
#### --distractors--
Quiz 2, question 2, distractor 1
---
Quiz 2, question 2, distractor 2
---
Quiz 2, question 2, distractor 3
#### --answer--
Quiz 2, question 2, answer
@@ -0,0 +1,24 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
::id{#key-for-css}
::id{#key-for-js}
```js
var x = 'y';
```
@@ -0,0 +1,27 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
::id{#key-for-css}
```css
body {
background: green;
}
```
::id{#key-for-js}
```js
var x = 'y';
```
@@ -0,0 +1,29 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
::id{#key-for-css}
```css
body {
background: green;
}
```
::id{#key-for-jsx}
```jsx
var x = 'y';
/* comment */
const Button = () => {
return <button> {/* another comment! */} text </button>;
};
```
@@ -0,0 +1,18 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
::id{#key-for-html}
@@ -0,0 +1,24 @@
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
::id{#key-for-css}
```css
body {
background: green;
}
```
::id{#key-for-js}
```js
var x = 'y';
```
@@ -0,0 +1,19 @@
# --description--
Paragraph 1
```html
code example
```
## --not-allowed--
should not be allowed
# --instructions--
Paragraph 0
```html
code example 0
```
@@ -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
@@ -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> code in </code> code tags
### --feedback--
Feedback line
## --video-solution--
3
@@ -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> code in </code> code tags
## --video-solution--
1
@@ -0,0 +1,43 @@
# --description--
Paragraph 1
```html
code example
```
# --instructions--
Paragraph 0
```html
code example 0
```
## --text--
Question line 1
```js
var x = 'y';
```
# --questions--
## --answers--
Some inline `code`
---
Some *italics*
A second answer paragraph.
---
<code> code in </code> code tags
## --video-solution--
3
@@ -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> code in </code> code tags
## --video-solution--
5
@@ -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> code in </code> code tags
## --video-solution--
3
@@ -0,0 +1,56 @@
# --description--
Paragraph 1
```html
code example
```
```yaml
key:
- subkey: value
anothersubkey: another value
```
# --instructions--
Paragraph 0
```html
code example 0
```
# --hints--
First hint
```js
// test code
```
Second hint with <code>code</code>
```js
// more test code
```
# --seed--
## --seed-contents--
```html
<html>
<body>
</body>
</html>
```
```css
body {
background: green;
}
```
```js
var x = 'y';
```
@@ -0,0 +1,19 @@
---
id: abc123
title: Step 1
challengeType: 28
---
# --description--
Some description.
# --hints--
```js
assert(true);
```
# --solutions--
```js
var x = 1;
```
@@ -0,0 +1,14 @@
---
id: abc456
title: Step 2
challengeType: 28
---
# --description--
Some description.
# --hints--
```js
assert(true);
```
@@ -0,0 +1,22 @@
---
id: abc789
title: Step 1
challengeType: 28
---
# --description--
Some description.
# --seed--
## --seed-contents--
```html
--fcc-editable-region--
<p>First region</p>
--fcc-editable-region--
<p>Middle</p>
--fcc-editable-region--
<p>Second region</p>
--fcc-editable-region--
```
@@ -0,0 +1,9 @@
---
id: step-1
title: Step 1
challengeType: 28
---
# --description--
Placeholder fixture for parser path resolution tests.
@@ -0,0 +1,670 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`challenge parser > should import md from other files 1`] = `
{
"assignments": [],
"challengeFiles": [
{
"contents": "<html>
<body>
</body>
</html>",
"editableRegionBoundaries": [],
"ext": "html",
"id": "",
"name": "index",
},
{
"contents": "body {
background: green;
}",
"editableRegionBoundaries": [],
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';
for (let index = 0; index < array.length; index++) {
const element = array[index];
// imported from script.md
}",
"editableRegionBoundaries": [],
"ext": "js",
"id": "custom-name",
"name": "script",
},
],
"description": "<section id="description">
<p>Paragraph 1</p>
<pre><code class="language-html">code example
</code></pre>
</section>",
"instructions": "<section id="instructions">
<p>Paragraph 0</p>
<pre><code class="language-html">code example 0
</code></pre>
</section>",
"solutions": [],
"tests": [
{
"testString": "// test code",
"text": "<p>First hint</p>",
},
{
"testString": "// more test code",
"text": "<p>Second hint with <code>code</code></p>",
},
],
}
`;
exports[`challenge parser > should not mix other YAML with the frontmatter 1`] = `
{
"assignments": [],
"challengeFiles": [
{
"contents": "<html>
<body>
</body>
</html>",
"editableRegionBoundaries": [],
"ext": "html",
"id": "",
"name": "index",
},
{
"contents": "body {
background: green;
}",
"editableRegionBoundaries": [],
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"editableRegionBoundaries": [],
"ext": "js",
"id": "",
"name": "script",
},
],
"description": "<section id="description">
<p>Paragraph 1</p>
<pre><code class="language-html">code example
</code></pre>
<pre><code class="language-yaml">key:
- subkey: value
anothersubkey: another value
</code></pre>
</section>",
"instructions": "<section id="instructions">
<p>Paragraph 0</p>
<pre><code class="language-html">code example 0
</code></pre>
</section>",
"solutions": [],
"tests": [
{
"testString": "// test code",
"text": "<p>First hint</p>",
},
{
"testString": "// more test code",
"text": "<p>Second hint with <code>code</code></p>",
},
],
}
`;
exports[`challenge parser > should not parse directives we do not use 1`] = `
{
"assignments": [],
"description": "<section id="description">
<p>:root appears, :import appears</p>
<p>the next paragraph should appear</p>
::import
<p>even though it's an import directive, but if we use the full syntax <code>::directive-name{attr="name" attr2="a/path"}</code></p>
<p>it goes.</p>
<p>::: name [inline-content] {key=val}
a container directive
:::</p>
</section>",
"solutions": [],
"tests": [],
}
`;
exports[`challenge parser > should parse a more realistic md file 1`] = `
{
"assignments": [],
"challengeFiles": [
{
"contents": "<html>
<body>
<h1>CatPhotoApp</h1>
<main>
<section>
<h2>Cat Photos</h2>
<!-- TODO: Add link to cat photos -->
<p>
Click here to view more
<a target="_blank" href="https://www.freecodecamp.org/cat-photos"
>cat photos</a
>.
</p>
<a href="https://www.freecodecamp.org/cat-photos"
><img
src="https://bit.ly/fcc-relaxing-cat"
alt="A cute orange cat lying on its back."
/></a>
</section>
<section>
<h2>Cat Lists</h2>
</section>
</main>
</body>
</html>",
"editableRegionBoundaries": [
19,
23,
],
"ext": "html",
"id": "html-key",
"name": "index",
},
{
"contents": "body {
background: white;
}
h1 {
font-size: 20px;
}
a {
color: green;
}",
"editableRegionBoundaries": [
7,
9,
],
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"editableRegionBoundaries": [],
"ext": "js",
"id": "final-key",
"name": "script",
},
],
"description": "<section id="description">
<p>When you add a lower rank heading element to the page, it's implied that you're starting a new subsection.</p>
<p>After the last <code>h2</code> element of the second <code>section</code> element, add an <code>h3</code> element with the text <code>Things cats love:</code>.</p>
<blockquote>
<p>Some text in a blockquote</p>
<p>
Some text in a blockquote, with <code>code</code>
</p>
</blockquote>
</section>",
"instructions": "<section id="instructions">
<p>Do something with the <code>code</code>.</p>
<p>To test that adjacent tags are handled correctly:</p>
<p>a bit of <code>code</code> <tag>with more after a space</tag> and another pair of <strong>elements</strong> <em>with a space</em></p>
</section>",
"solutions": [
[
{
"contents": "<html>
<body>
<h1>CatPhotoApp</h1>
<main>
<section>
<h2>Cat Photos</h2>
<!-- TODO: Add link to cat photos -->
<p>
Click here to view more
<a target="_blank" href="https://www.freecodecamp.org/cat-photos"
>cat photos</a
>.
</p>
<a href="https://www.freecodecamp.org/cat-photos"
><img
src="https://bit.ly/fcc-relaxing-cat"
alt="A cute orange cat lying on its back."
/></a>
</section>
<section>
<h2>Cat Lists</h2>
</section>
</main>
</body>
</html>",
"ext": "html",
"id": "html-key",
"name": "index",
},
{
"contents": "body {
background: white;
}
h1 {
font-size: 20px;
}
a {
color: green;
}",
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"ext": "js",
"id": "final-key",
"name": "script",
},
],
],
"tests": [
{
"testString": "assert(
document.querySelectorAll('main > section')[1] &&
code.match(/\\<\\/section>/g).length == 2
);",
"text": "<p>The second <code>section</code> element appears to be missing or does not have both an opening and closing tag.</p>",
},
{
"testString": "assert(
document.querySelectorAll('main > section')[1].lastElementChild.nodeName ===
'H3'
);",
"text": "<p>There should be an <code>h3</code> element right above the second <code>section</code> element's closing tag.</p>",
},
{
"testString": "assert(
document
.querySelectorAll('main > section')[1]
.lastElementChild.innerText.toLowerCase()
.replace(/\\s+/g, ' ') === 'things cats love:'
);",
"text": "<p>The <code>h3</code> element right above the second <code>section</code> element's closing tag should have the text <code>Things cats love:</code>. Make sure to include the colon at the end of the text.</p>",
},
{
"testString": "const secondSectionLastElemNode = document.querySelectorAll('main > section')[1]
.lastElementChild;
assert(
secondSectionLastElemNode.nodeName === 'H3' &&
secondSectionLastElemNode.previousElementSibling.innerText
.toLowerCase()
.replace(/\\s+/g, ' ') === 'cat lists'
);",
"text": "<p>There should be an <code>h2</code> element with the text <code>Cat Lists</code> above the last <code>h3</code> element that is nested in the last <code>section</code> element'. You may have accidentally deleted the <code>h2</code> element.</p>",
},
],
}
`;
exports[`challenge parser > should parse a simple md file 1`] = `
{
"assignments": [],
"challengeFiles": [
{
"contents": "<html>
<body>
</body>
</html>",
"editableRegionBoundaries": [],
"ext": "html",
"id": "",
"name": "index",
},
{
"contents": "body {
background: green;
}",
"editableRegionBoundaries": [],
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"editableRegionBoundaries": [],
"ext": "js",
"id": "",
"name": "script",
},
{
"contents": "{
"compilerOptions": {
"target": "ES2020"
}
}",
"editableRegionBoundaries": [],
"ext": "json",
"id": "",
"name": "tsconfig",
},
],
"description": "<section id="description">
<p>Paragraph 1</p>
<pre><code class="language-html">code example
</code></pre>
</section>",
"instructions": "<section id="instructions">
<p>Paragraph 0</p>
<pre><code class="language-html">code example 0
</code></pre>
</section>",
"solutions": [
[
{
"contents": "<html>
<body>
</body>
</html>",
"ext": "html",
"id": "html-key",
"name": "index",
},
{
"contents": "body {
background: white;
}",
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"ext": "js",
"id": "",
"name": "script",
},
],
],
"tests": [
{
"testString": "// test code",
"text": "<p>First hint</p>",
},
{
"testString": "// more test code",
"text": "<p>Second hint with <code>code</code></p>",
},
{
"testString": "// more test code
if(let x of xs) {
console.log(x);
}",
"text": "<p>Third <em>hint</em> with <code>code</code> and <code>inline code</code></p>",
},
],
}
`;
exports[`challenge parser > should parse frontmatter 1`] = `
{
"assignments": [],
"challengeFiles": [
{
"contents": "<html>
<body>
</body>
</html>",
"editableRegionBoundaries": [],
"ext": "html",
"id": "",
"name": "index",
},
{
"contents": "body {
background: green;
}",
"editableRegionBoundaries": [],
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"editableRegionBoundaries": [],
"ext": "js",
"id": "",
"name": "script",
},
],
"challengeType": 0,
"description": "<section id="description">
<p>Paragraph 1</p>
<pre><code class="language-html">code example
</code></pre>
</section>",
"forumTopicId": 18276,
"id": "bd7123c8c441eddfaeb5bdef",
"isHidden": false,
"solutions": [],
"tests": [
{
"testString": "// test code",
"text": "<p>First hint</p>",
},
{
"testString": "// more test code",
"text": "<p>Second hint with <code>code</code></p>",
},
],
"title": "Say Hello to HTML Elements",
"videoUrl": "https://scrimba.com/p/pVMPUv/cE8Gpt2",
}
`;
exports[`challenge parser > should parse gfm strikethrough and frontmatter 1`] = `
{
"assignments": [],
"challengeFiles": [
{
"contents": "<html>
<body>
</body>
</html>",
"editableRegionBoundaries": [],
"ext": "html",
"id": "",
"name": "index",
},
{
"contents": "body {
background: green;
}",
"editableRegionBoundaries": [],
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"editableRegionBoundaries": [],
"ext": "js",
"id": "",
"name": "script",
},
],
"description": "<section id="description">
<p>Paragraph 1 <del>Strikethrough text</del>. https://should.not.be.autolinked</p>
<pre><code class="language-html">code example
</code></pre>
<table>
<thead>
<tr>
<th>example</th>
<th>of a</th>
</tr>
</thead>
<tbody>
<tr>
<td>gfm</td>
<td>table</td>
</tr>
</tbody>
</table>
</section>",
"instructions": "<section id="instructions">
<p>Paragraph 0</p>
<pre><code class="language-html">code example 0
</code></pre>
</section>",
"solutions": [
[
{
"contents": "<html>
<body>
</body>
</html>",
"ext": "html",
"id": "html-key",
"name": "index",
},
{
"contents": "body {
background: white;
}",
"ext": "css",
"id": "",
"name": "styles",
},
{
"contents": "var x = 'y';",
"ext": "js",
"id": "",
"name": "script",
},
],
],
"tests": [
{
"testString": "// test code",
"text": "<p>First hint</p>",
},
{
"testString": "// more test code",
"text": "<p>Second hint with <code>code</code></p>",
},
{
"testString": "// more test code
if(let x of xs) {
console.log(x);
}",
"text": "<p>Third <em>hint</em> with <code>code</code> and <code>inline code</code></p>",
},
],
}
`;
exports[`challenge parser > should parse md with a scene 1`] = `
{
"assignments": [],
"description": "<section id="description">
<p>This challenge has a scene.</p>
</section>",
"scene": {
"commands": [
{
"character": "Maria",
"opacity": 1,
"startTime": 0,
},
{
"character": "Maria",
"dialogue": {
"align": "center",
"text": "I'm Maria, the team lead.",
},
"finishTime": 2.4,
"startTime": 0.7,
},
{
"character": "Maria",
"opacity": 0,
"startTime": 3.4,
},
],
"setup": {
"audio": {
"filename": "1.1-1.mp3",
"finishTimestamp": 4,
"startTime": 1,
"startTimestamp": 2.6,
},
"background": "company2-center.png",
"characters": [
{
"character": "Maria",
"opacity": 0,
"position": {
"x": 50,
"y": 0,
"z": 1.5,
},
},
],
},
},
"solutions": [],
"tests": [],
}
`;
exports[`challenge parser > should parse video questions 1`] = `
{
"assignments": [],
"description": "<section id="description">
<p>Paragraph 1</p>
<pre><code class="language-html">code example
</code></pre>
</section>",
"instructions": "<section id="instructions">
<p>Paragraph 0</p>
<pre><code class="language-html">code example 0
</code></pre>
</section>",
"questions": [
{
"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,
},
],
"solution": 3,
"text": "<p>Question line 1</p>
<pre><code class="language-js"> var x = 'y';
</code></pre>",
},
],
"solutions": [],
"tests": [],
}
`;
@@ -0,0 +1,66 @@
import { resolve } from 'path';
import { describe, it, expect } from 'vitest';
import { parseMD } from '.';
describe('challenge parser', () => {
it('should parse a simple md file', async () => {
const parsed = await parseMD(resolve(__dirname, '__fixtures__/simple.md'));
expect(parsed).toMatchSnapshot();
});
it('should parse a more realistic md file', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/realistic.md')
);
expect(parsed).toMatchSnapshot();
});
it('should import md from other files', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/with-imports.md')
);
expect(parsed).toMatchSnapshot();
});
it('should parse frontmatter', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/with-frontmatter.md')
);
expect(parsed).toMatchSnapshot();
});
it('should parse gfm strikethrough and frontmatter', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/with-gfm.md')
);
expect(parsed).toMatchSnapshot();
});
it('should not mix other YAML with the frontmatter', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/with-yaml.md')
);
expect(parsed).toMatchSnapshot();
});
it('should parse video questions', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/with-video-question.md')
);
expect(parsed).toMatchSnapshot();
});
it('should not parse directives we do not use', async () => {
const parsed = await parseMD(
resolve(__dirname, '__fixtures__/with-directives.md')
);
expect(parsed).toMatchSnapshot();
});
it('should parse md with a scene', async () => {
const parsed = await parseMD(resolve(__dirname, '__fixtures__/scene.md'));
expect(parsed).toMatchSnapshot();
});
});
+38
View File
@@ -0,0 +1,38 @@
// TypeScript declaration file for challenge-parser/parser
// This module exports functions to parse challenge markdown files
type ChallengeFile = {
name: string;
contents: string;
ext: string;
editableRegionBoundaries: number[];
};
export interface ParsedChallenge {
id: string;
title: string;
challengeType: number;
description?: string;
instructions?: string;
questions?: string[];
challengeFiles?: ChallengeFile[];
solutions?: {
contents: string;
ext: string;
name: string;
}[][];
[key: string]: unknown; // Allow for additional properties that may be added by plugins
}
/**
* Parses a markdown challenge file asynchronously
* @param filename - Path to the markdown file to parse
* @returns Promise that resolves to the parsed challenge data
*/
export function parseMD(filename: string): Promise<ParsedChallenge>;
/**
* Parses a markdown challenge file synchronously
* @param filename - Path to the markdown file to parse
* @returns The parsed challenge data
*/
export function parseMDSync(filename: string): ParsedChallenge;
+97
View File
@@ -0,0 +1,97 @@
const directive = require('remark-directive');
const frontmatter = require('remark-frontmatter');
const remark = require('remark-parse');
const { readSync } = require('to-vfile');
const unified = require('unified');
const addFillInTheBlank = require('./plugins/add-fill-in-the-blank');
const addFrontmatter = require('./plugins/add-frontmatter');
const validateSections = require('./plugins/validate-sections');
const addSeed = require('./plugins/add-seed');
const addSolution = require('./plugins/add-solution');
const addHooks = require('./plugins/add-hooks');
const addTests = require('./plugins/add-tests');
const addText = require('./plugins/add-text');
const addVideoQuestion = require('./plugins/add-video-question');
const addAssignment = require('./plugins/add-assignment');
const replaceImports = require('./plugins/replace-imports');
const restoreDirectives = require('./plugins/restore-directives');
const tableAndStrikeThrough = require('./plugins/table-and-strikethrough');
const addScene = require('./plugins/add-scene');
const addQuizzes = require('./plugins/add-quizzes');
const addInteractiveElements = require('./plugins/add-interactive-elements');
// by convention, anything that adds to file.data has the name add<name>.
const processor = unified()
// add the remark parser
.use(remark)
// modify the parser so that Github flavour tables and strikethroughs get
// converted to 'delete' nodes
.use(tableAndStrikeThrough)
// directives are parsed into 'leafDirective' nodes and used for imports
.use(directive)
// convert the text at the top of the document (surrounded by ---) into a
// 'yaml' node
.use(frontmatter, ['yaml'])
// extract the content from that 'yaml' node
.use(addFrontmatter)
// validate all section markers before any plugin tries to extract sections
.use(validateSections)
// Any imports will be replaced (in the tree) with
// the sub-tree of the target file. e.g.
// ::import{component="Script" from="./file.path" }
// means that file.path's tree will be inserted wherever
// ::use{component="Script"}
// appears.
.use(replaceImports)
// the final five 'add' plugins insert content into file.data
// TODO: rename test->hint everywhere? It should make things easier to reason
// about.
.use(addSeed)
.use(addSolution)
.use(addInteractiveElements)
// the directives will have been parsed and used by this point, any remaining
// 'directives' will be from text like the css selector :root. These should be
// converted back to text before they're added to the challenge object.
.use(restoreDirectives)
.use(addFillInTheBlank)
.use(addVideoQuestion)
.use(addAssignment)
.use(addScene)
.use(addQuizzes)
.use(addHooks)
.use(addTests)
.use(addText, [
'description',
'instructions',
'notes',
'explanation',
'transcript'
]);
exports.parseMD = function parseMD(filename) {
return new Promise((resolve, reject) => {
const file = readSync(filename);
const tree = processor.parse(file);
processor.run(tree, file, function (err, node, file) {
if (!err) {
resolve(file.data);
} else {
err.message += ' in file ' + filename;
reject(err);
}
});
});
};
exports.parseMDSync = function parseMDSync(filename) {
const file = readSync(filename);
const tree = processor.parse(file);
try {
processor.runSync(tree, file);
} catch (err) {
err.message += ' in file ' + filename;
throw err;
}
return file.data;
};
@@ -0,0 +1,12 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`process-frontmatter plugin > should have an output to match the snapshot 1`] = `
{
"challengeType": 0,
"forumTopicId": 18276,
"id": "bd7123c8c441eddfaeb5bdef",
"isHidden": false,
"title": "Say Hello to HTML Elements",
"videoUrl": "https://scrimba.com/p/pVMPUv/cE8Gpt2",
}
`;
@@ -0,0 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`add-before-hook plugin > should have an output to match the snapshot 1`] = `
{
"hooks": {
"beforeAll": "// before all code
function foo() {
return 'bar';
}
foo();",
},
}
`;

Some files were not shown because too many files have changed in this diff Show More