feat: add a markdown parser for challenges
This commit is contained in:
parent
77d057d4e5
commit
f022177352
@ -0,0 +1,10 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`frontmatter-to-data plugin should have an output to match the snapshot 1`] = `
|
||||
Object {
|
||||
"challengeType": 0,
|
||||
"id": "bd7123c8c441eddfaeb5bdef",
|
||||
"title": "Say Hello to HTML Elements",
|
||||
"videoUrl": "https://scrimba.com/p/pVMPUv/cE8Gpt2",
|
||||
}
|
||||
`;
|
@ -0,0 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`tests-to-data plugin should have an output to match the snapshot 1`] = `
|
||||
Object {
|
||||
"tests": Array [
|
||||
Object {
|
||||
"testString": "assert.isTrue((/hello(\\\\s)+world/gi).test($('h1').text()), 'Your <code>h1</code> element should have the text \\"Hello World\\".');",
|
||||
"text": "Your <code>h1</code> element should have the text \\"Hello World\\".",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`text-to-data should have an output to match the snapshot 1`] = `
|
||||
Object {
|
||||
"description": "<section id=\\"description\\">
|
||||
<p>Welcome to freeCodeCamp's HTML coding challenges. These will walk you through web development step-by-step.</p>
|
||||
<p>Lorem Ipsum with <code>some code</code></p>
|
||||
<blockquote>
|
||||
<p>Some text in a blockquote</p>
|
||||
<p>Some text in a blockquote, with <code>code</code></p>
|
||||
</blockquote>
|
||||
<pre><code class=\\"language-html\\"><p>We aim to preserve this</p>
|
||||
</code></pre>
|
||||
</section>",
|
||||
"instructions": "<section id=\\"instructions\\">
|
||||
<p>To pass the test on this challenge, change your <code>h1</code> element's text to say \\"Hello World\\".</p>
|
||||
</section>",
|
||||
}
|
||||
`;
|
1132
curriculum/challenge-md-parser/fixtures/challenge-html-ast.json
Normal file
1132
curriculum/challenge-md-parser/fixtures/challenge-html-ast.json
Normal file
File diff suppressed because it is too large
Load Diff
915
curriculum/challenge-md-parser/fixtures/challenge-md-ast.json
Normal file
915
curriculum/challenge-md-parser/fixtures/challenge-md-ast.json
Normal file
@ -0,0 +1,915 @@
|
||||
{
|
||||
"type": "root",
|
||||
"children": [
|
||||
{
|
||||
"type": "yaml",
|
||||
"value":
|
||||
"id: bd7123c8c441eddfaeb5bdef\ntitle: Say Hello to HTML Elements\nchallengeType: 0\nvideoUrl: https://scrimba.com/p/pVMPUv/cE8Gpt2",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"column": 4,
|
||||
"offset": 134
|
||||
},
|
||||
"indent": [1, 1, 1, 1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 2,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Description",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 4,
|
||||
"offset": 139
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 15,
|
||||
"offset": 150
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 1,
|
||||
"offset": 136
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 15,
|
||||
"offset": 150
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<section id='description'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 9,
|
||||
"column": 1,
|
||||
"offset": 151
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"column": 27,
|
||||
"offset": 177
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value":
|
||||
"Welcome to freeCodeCamp's HTML coding challenges. These will walk you through web development step-by-step.",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 11,
|
||||
"column": 1,
|
||||
"offset": 179
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"column": 108,
|
||||
"offset": 286
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 11,
|
||||
"column": 1,
|
||||
"offset": 179
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"column": 108,
|
||||
"offset": 286
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Lorem Ipsum with ",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 13,
|
||||
"column": 1,
|
||||
"offset": 288
|
||||
},
|
||||
"end": {
|
||||
"line": 13,
|
||||
"column": 18,
|
||||
"offset": 305
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "inlineCode",
|
||||
"value": "some code",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 13,
|
||||
"column": 18,
|
||||
"offset": 305
|
||||
},
|
||||
"end": {
|
||||
"line": 13,
|
||||
"column": 29,
|
||||
"offset": 316
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 13,
|
||||
"column": 1,
|
||||
"offset": 288
|
||||
},
|
||||
"end": {
|
||||
"line": 13,
|
||||
"column": 29,
|
||||
"offset": 316
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "blockquote",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Some text in a blockquote",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"column": 3,
|
||||
"offset": 320
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"column": 28,
|
||||
"offset": 345
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"column": 3,
|
||||
"offset": 320
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"column": 28,
|
||||
"offset": 345
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Some text in a blockquote, with ",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 17,
|
||||
"column": 3,
|
||||
"offset": 349
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"column": 35,
|
||||
"offset": 381
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "inlineCode",
|
||||
"value": "code",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 17,
|
||||
"column": 35,
|
||||
"offset": 381
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"column": 41,
|
||||
"offset": 387
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 17,
|
||||
"column": 3,
|
||||
"offset": 349
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"column": 41,
|
||||
"offset": 387
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"column": 1,
|
||||
"offset": 318
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"column": 41,
|
||||
"offset": 387
|
||||
},
|
||||
"indent": [1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "code",
|
||||
"lang": "html",
|
||||
"value": "<p>We aim to preserve this</p>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 19,
|
||||
"column": 1,
|
||||
"offset": 389
|
||||
},
|
||||
"end": {
|
||||
"line": 21,
|
||||
"column": 4,
|
||||
"offset": 431
|
||||
},
|
||||
"indent": [1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</section>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 22,
|
||||
"column": 1,
|
||||
"offset": 432
|
||||
},
|
||||
"end": {
|
||||
"line": 22,
|
||||
"column": 11,
|
||||
"offset": 442
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 2,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Instructions",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 24,
|
||||
"column": 4,
|
||||
"offset": 447
|
||||
},
|
||||
"end": {
|
||||
"line": 24,
|
||||
"column": 16,
|
||||
"offset": 459
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 24,
|
||||
"column": 1,
|
||||
"offset": 444
|
||||
},
|
||||
"end": {
|
||||
"line": 24,
|
||||
"column": 16,
|
||||
"offset": 459
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<section id='instructions'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 25,
|
||||
"column": 1,
|
||||
"offset": 460
|
||||
},
|
||||
"end": {
|
||||
"line": 25,
|
||||
"column": 28,
|
||||
"offset": 487
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "To pass the test on this challenge, change your ",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 27,
|
||||
"column": 1,
|
||||
"offset": 489
|
||||
},
|
||||
"end": {
|
||||
"line": 27,
|
||||
"column": 49,
|
||||
"offset": 537
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "inlineCode",
|
||||
"value": "h1",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 27,
|
||||
"column": 49,
|
||||
"offset": 537
|
||||
},
|
||||
"end": {
|
||||
"line": 27,
|
||||
"column": 53,
|
||||
"offset": 541
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"value": " element's text to say \"Hello World\".",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 27,
|
||||
"column": 53,
|
||||
"offset": 541
|
||||
},
|
||||
"end": {
|
||||
"line": 27,
|
||||
"column": 90,
|
||||
"offset": 578
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 27,
|
||||
"column": 1,
|
||||
"offset": 489
|
||||
},
|
||||
"end": {
|
||||
"line": 27,
|
||||
"column": 90,
|
||||
"offset": 578
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</section>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 29,
|
||||
"column": 1,
|
||||
"offset": 580
|
||||
},
|
||||
"end": {
|
||||
"line": 29,
|
||||
"column": 11,
|
||||
"offset": 590
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 2,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Tests",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 31,
|
||||
"column": 4,
|
||||
"offset": 595
|
||||
},
|
||||
"end": {
|
||||
"line": 31,
|
||||
"column": 9,
|
||||
"offset": 600
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 31,
|
||||
"column": 1,
|
||||
"offset": 592
|
||||
},
|
||||
"end": {
|
||||
"line": 31,
|
||||
"column": 9,
|
||||
"offset": 600
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<section id='tests'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 32,
|
||||
"column": 1,
|
||||
"offset": 601
|
||||
},
|
||||
"end": {
|
||||
"line": 32,
|
||||
"column": 21,
|
||||
"offset": 621
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "code",
|
||||
"lang": "yml",
|
||||
"value":
|
||||
"tests:\n - text: Your <code>h1</code> element should have the text \"Hello World\".\n testString: assert.isTrue((/hello(\\s)+world/gi).test($('h1').text()), 'Your <code>h1</code> element should have the text \"Hello World\".');",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 34,
|
||||
"column": 1,
|
||||
"offset": 623
|
||||
},
|
||||
"end": {
|
||||
"line": 38,
|
||||
"column": 4,
|
||||
"offset": 858
|
||||
},
|
||||
"indent": [1, 1, 1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</section>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 40,
|
||||
"column": 1,
|
||||
"offset": 860
|
||||
},
|
||||
"end": {
|
||||
"line": 40,
|
||||
"column": 11,
|
||||
"offset": 870
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 2,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Challenge Seed",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 42,
|
||||
"column": 4,
|
||||
"offset": 875
|
||||
},
|
||||
"end": {
|
||||
"line": 42,
|
||||
"column": 18,
|
||||
"offset": 889
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 42,
|
||||
"column": 1,
|
||||
"offset": 872
|
||||
},
|
||||
"end": {
|
||||
"line": 42,
|
||||
"column": 18,
|
||||
"offset": 889
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<section id='challengeSeed'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 43,
|
||||
"column": 1,
|
||||
"offset": 890
|
||||
},
|
||||
"end": {
|
||||
"line": 43,
|
||||
"column": 29,
|
||||
"offset": 918
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<div id='js-seed'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 45,
|
||||
"column": 1,
|
||||
"offset": 920
|
||||
},
|
||||
"end": {
|
||||
"line": 45,
|
||||
"column": 19,
|
||||
"offset": 938
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "code",
|
||||
"lang": "js",
|
||||
"value":
|
||||
"function testFunction(arg) {\n return arg;\n}\n\ntestFunction('hello');",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 47,
|
||||
"column": 1,
|
||||
"offset": 940
|
||||
},
|
||||
"end": {
|
||||
"line": 53,
|
||||
"column": 4,
|
||||
"offset": 1018
|
||||
},
|
||||
"indent": [1, 1, 1, 1, 1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</div>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 55,
|
||||
"column": 1,
|
||||
"offset": 1020
|
||||
},
|
||||
"end": {
|
||||
"line": 55,
|
||||
"column": 7,
|
||||
"offset": 1026
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 3,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Before Test",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 57,
|
||||
"column": 5,
|
||||
"offset": 1032
|
||||
},
|
||||
"end": {
|
||||
"line": 57,
|
||||
"column": 16,
|
||||
"offset": 1043
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 57,
|
||||
"column": 1,
|
||||
"offset": 1028
|
||||
},
|
||||
"end": {
|
||||
"line": 57,
|
||||
"column": 16,
|
||||
"offset": 1043
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<div id='js-setup'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 58,
|
||||
"column": 1,
|
||||
"offset": 1044
|
||||
},
|
||||
"end": {
|
||||
"line": 58,
|
||||
"column": 20,
|
||||
"offset": 1063
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "code",
|
||||
"lang": "js",
|
||||
"value": "console.log('before the test');",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 60,
|
||||
"column": 1,
|
||||
"offset": 1065
|
||||
},
|
||||
"end": {
|
||||
"line": 62,
|
||||
"column": 4,
|
||||
"offset": 1106
|
||||
},
|
||||
"indent": [1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</div>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 64,
|
||||
"column": 1,
|
||||
"offset": 1108
|
||||
},
|
||||
"end": {
|
||||
"line": 64,
|
||||
"column": 7,
|
||||
"offset": 1114
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 3,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "After Test",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 66,
|
||||
"column": 5,
|
||||
"offset": 1120
|
||||
},
|
||||
"end": {
|
||||
"line": 66,
|
||||
"column": 15,
|
||||
"offset": 1130
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 66,
|
||||
"column": 1,
|
||||
"offset": 1116
|
||||
},
|
||||
"end": {
|
||||
"line": 66,
|
||||
"column": 15,
|
||||
"offset": 1130
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<div id='js-teardown'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 67,
|
||||
"column": 1,
|
||||
"offset": 1131
|
||||
},
|
||||
"end": {
|
||||
"line": 67,
|
||||
"column": 23,
|
||||
"offset": 1153
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "code",
|
||||
"lang": "js",
|
||||
"value": "console.info('after the test');",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 69,
|
||||
"column": 1,
|
||||
"offset": 1155
|
||||
},
|
||||
"end": {
|
||||
"line": 71,
|
||||
"column": 4,
|
||||
"offset": 1196
|
||||
},
|
||||
"indent": [1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</div>\n</section>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 73,
|
||||
"column": 1,
|
||||
"offset": 1198
|
||||
},
|
||||
"end": {
|
||||
"line": 74,
|
||||
"column": 11,
|
||||
"offset": 1215
|
||||
},
|
||||
"indent": [1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "heading",
|
||||
"depth": 2,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"value": "Solution",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 76,
|
||||
"column": 4,
|
||||
"offset": 1220
|
||||
},
|
||||
"end": {
|
||||
"line": 76,
|
||||
"column": 12,
|
||||
"offset": 1228
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 76,
|
||||
"column": 1,
|
||||
"offset": 1217
|
||||
},
|
||||
"end": {
|
||||
"line": 76,
|
||||
"column": 12,
|
||||
"offset": 1228
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "<section id='solution'>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 77,
|
||||
"column": 1,
|
||||
"offset": 1229
|
||||
},
|
||||
"end": {
|
||||
"line": 77,
|
||||
"column": 24,
|
||||
"offset": 1252
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "code",
|
||||
"lang": "js",
|
||||
"value":
|
||||
"function testFunction(arg) {\n return arg;\n}\n\ntestFunction('hello');",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 79,
|
||||
"column": 1,
|
||||
"offset": 1254
|
||||
},
|
||||
"end": {
|
||||
"line": 85,
|
||||
"column": 4,
|
||||
"offset": 1332
|
||||
},
|
||||
"indent": [1, 1, 1, 1, 1, 1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "html",
|
||||
"value": "</section>",
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 86,
|
||||
"column": 1,
|
||||
"offset": 1333
|
||||
},
|
||||
"end": {
|
||||
"line": 86,
|
||||
"column": 11,
|
||||
"offset": 1343
|
||||
},
|
||||
"indent": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 1,
|
||||
"offset": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 86,
|
||||
"column": 11,
|
||||
"offset": 1343
|
||||
}
|
||||
}
|
||||
}
|
18
curriculum/challenge-md-parser/frontmatter-to-data.js
Normal file
18
curriculum/challenge-md-parser/frontmatter-to-data.js
Normal file
@ -0,0 +1,18 @@
|
||||
const visit = require('unist-util-visit');
|
||||
const YAML = require('js-yaml');
|
||||
|
||||
function plugin() {
|
||||
return transformer;
|
||||
|
||||
function transformer(tree, file) {
|
||||
visit(tree, 'yaml', visitor);
|
||||
|
||||
function visitor(node) {
|
||||
const frontmatter = YAML.load(node.value);
|
||||
|
||||
file.data = { ...file.data, ...frontmatter };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = plugin;
|
62
curriculum/challenge-md-parser/frontmatter-to-data.test.js
Normal file
62
curriculum/challenge-md-parser/frontmatter-to-data.test.js
Normal file
@ -0,0 +1,62 @@
|
||||
/* global describe it expect beforeEach */
|
||||
const { isObject } = require('lodash');
|
||||
|
||||
const mockAST = require('./fixtures/challenge-md-ast.json');
|
||||
const frontmatterToData = require('./frontmatter-to-data');
|
||||
|
||||
describe('frontmatter-to-data plugin', () => {
|
||||
const plugin = frontmatterToData();
|
||||
let file = { data: {} };
|
||||
beforeEach(() => {
|
||||
file = { data: {} };
|
||||
});
|
||||
|
||||
it('should return a plugin which is a function', () => {
|
||||
expect(typeof plugin).toEqual('function');
|
||||
});
|
||||
|
||||
it('should maintain an object for the `file.data` property', () => {
|
||||
plugin(mockAST, file);
|
||||
expect(isObject(file.data)).toBe(true);
|
||||
});
|
||||
|
||||
it('should add all keys from frontmatter to the `file.data` property', () => {
|
||||
const expectedKeys = ['id', 'title', 'challengeType', 'videoUrl'];
|
||||
plugin(mockAST, file);
|
||||
const actualKeys = Object.keys(file.data);
|
||||
expect(actualKeys).toEqual(expectedKeys);
|
||||
});
|
||||
|
||||
it('should not mutate any type held in the frontmatter', () => {
|
||||
expect.assertions(4);
|
||||
plugin(mockAST, file);
|
||||
const { id, title, challengeType, videoUrl } = file.data;
|
||||
expect(typeof id).toEqual('string');
|
||||
expect(typeof title).toEqual('string');
|
||||
expect(typeof challengeType).toEqual('number');
|
||||
expect(typeof videoUrl).toEqual('string');
|
||||
});
|
||||
|
||||
it('should trim extra whitespace from keys and values', () => {
|
||||
expect.assertions(7);
|
||||
plugin(mockAST, file);
|
||||
const whitespaceRE = /(^\s\S+|\S\s$)/;
|
||||
const keys = Object.keys(file.data);
|
||||
keys.forEach(key => expect(whitespaceRE.test(key)).toBe(false));
|
||||
const values = keys.map(key => file.data[key]);
|
||||
values
|
||||
.filter(value => typeof value === 'string')
|
||||
.forEach(value => expect(whitespaceRE.test(value)).toBe(false));
|
||||
});
|
||||
|
||||
it('should not mutate url strings', () => {
|
||||
const expectedUrl = 'https://scrimba.com/p/pVMPUv/cE8Gpt2';
|
||||
plugin(mockAST, file);
|
||||
expect(file.data.videoUrl).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should have an output to match the snapshot', () => {
|
||||
plugin(mockAST, file);
|
||||
expect(file.data).toMatchSnapshot();
|
||||
});
|
||||
});
|
32
curriculum/challenge-md-parser/index.js
Normal file
32
curriculum/challenge-md-parser/index.js
Normal file
@ -0,0 +1,32 @@
|
||||
const unified = require('unified');
|
||||
const vfile = require('to-vfile');
|
||||
const report = require('vfile-reporter');
|
||||
const markdown = require('remark-parse');
|
||||
const remark2rehype = require('remark-rehype');
|
||||
const html = require('rehype-stringify');
|
||||
const frontmatter = require('remark-frontmatter');
|
||||
const raw = require('rehype-raw');
|
||||
|
||||
const frontmatterToData = require('./frontmatter-to-data');
|
||||
const textToData = require('./text-to-data');
|
||||
const testsToData = require('./tests-to-data');
|
||||
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(frontmatter, ['yaml'])
|
||||
.use(frontmatterToData)
|
||||
.use(testsToData)
|
||||
.use(remark2rehype, { allowDangerousHTML: true })
|
||||
.use(raw)
|
||||
.use(textToData, ['description', 'instructions'])
|
||||
// the plugins below are just to stop the processor from throwing
|
||||
// we need to write a compiler that can create graphql nodes
|
||||
.use(html);
|
||||
|
||||
processor.process(vfile.readSync('maybe.md'), function(err, file) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.error(report(file));
|
||||
console.log(JSON.stringify(file.data, null, 2));
|
||||
});
|
86
curriculum/challenge-md-parser/maybe.md
Normal file
86
curriculum/challenge-md-parser/maybe.md
Normal file
@ -0,0 +1,86 @@
|
||||
---
|
||||
id: bd7123c8c441eddfaeb5bdef
|
||||
title: Say Hello to HTML Elements
|
||||
challengeType: 0
|
||||
videoUrl: https://scrimba.com/p/pVMPUv/cE8Gpt2
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id='description'>
|
||||
|
||||
Welcome to freeCodeCamp's HTML coding challenges. These will walk you through web development step-by-step.
|
||||
|
||||
Lorem Ipsum with `some code`
|
||||
|
||||
> Some text in a blockquote
|
||||
|
||||
> Some text in a blockquote, with `code`
|
||||
|
||||
```html
|
||||
<p>We aim to preserve this</p>
|
||||
```
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id='instructions'>
|
||||
|
||||
To pass the test on this challenge, change your `h1` element's text to say "Hello World".
|
||||
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: Your <code>h1</code> element should have the text "Hello World".
|
||||
testString: assert.isTrue((/hello(\s)+world/gi).test($('h1').text()), 'Your <code>h1</code> element should have the text "Hello World".');
|
||||
```
|
||||
|
||||
</section>
|
||||
|
||||
## Challenge Seed
|
||||
<section id='challengeSeed'>
|
||||
|
||||
<div id='js-seed'>
|
||||
|
||||
```js
|
||||
function testFunction(arg) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
testFunction('hello');
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Before Test
|
||||
<div id='js-setup'>
|
||||
|
||||
```js
|
||||
console.log('before the test');
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### After Test
|
||||
<div id='js-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
```
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```js
|
||||
function testFunction(arg) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
testFunction('hello');
|
||||
```
|
||||
</section>
|
6142
curriculum/challenge-md-parser/package-lock.json
generated
Normal file
6142
curriculum/challenge-md-parser/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
curriculum/challenge-md-parser/package.json
Normal file
29
curriculum/challenge-md-parser/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "migration",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"jest": "^23.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"hast-util-to-html": "^4.0.1",
|
||||
"js-yaml": "^3.12.0",
|
||||
"lodash": "^4.17.11",
|
||||
"rehype-raw": "^3.0.0",
|
||||
"rehype-stringify": "^4.0.0",
|
||||
"remark-frontmatter": "^1.3.0",
|
||||
"remark-parse": "^5.0.0",
|
||||
"remark-rehype": "^3.0.1",
|
||||
"to-vfile": "^5.0.1",
|
||||
"unified": "^7.0.0",
|
||||
"unist-util-visit": "^1.4.0",
|
||||
"vfile-reporter": "^5.0.0"
|
||||
}
|
||||
}
|
23
curriculum/challenge-md-parser/tests-to-data.js
Normal file
23
curriculum/challenge-md-parser/tests-to-data.js
Normal file
@ -0,0 +1,23 @@
|
||||
const visit = require('unist-util-visit');
|
||||
const YAML = require('js-yaml');
|
||||
|
||||
function plugin() {
|
||||
return transformer;
|
||||
|
||||
function transformer(tree, file) {
|
||||
visit(tree, 'code', visitor);
|
||||
|
||||
function visitor(node) {
|
||||
const { lang, value } = node;
|
||||
if (lang === 'yml') {
|
||||
const tests = YAML.load(value);
|
||||
file.data = {
|
||||
...file.data,
|
||||
...tests
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = plugin;
|
36
curriculum/challenge-md-parser/tests-to-data.test.js
Normal file
36
curriculum/challenge-md-parser/tests-to-data.test.js
Normal file
@ -0,0 +1,36 @@
|
||||
/* global describe it expect beforeEach */
|
||||
const mockAST = require('./fixtures/challenge-md-ast.json');
|
||||
const testsToData = require('./tests-to-data');
|
||||
|
||||
describe('tests-to-data plugin', () => {
|
||||
const plugin = testsToData();
|
||||
let file = { data: {} };
|
||||
|
||||
beforeEach(() => {
|
||||
file = { data: {} };
|
||||
});
|
||||
|
||||
it('returns a function', () => {
|
||||
expect(typeof plugin).toEqual('function');
|
||||
});
|
||||
|
||||
it('adds a `tests` property to `file.data`', () => {
|
||||
plugin(mockAST, file);
|
||||
|
||||
expect('tests' in file.data).toBe(true);
|
||||
});
|
||||
|
||||
it('adds test objects to the tests array following a schema', () => {
|
||||
expect.assertions(3);
|
||||
plugin(mockAST, file);
|
||||
const testObject = file.data.tests[0];
|
||||
expect(Object.keys(testObject).length).toBe(2);
|
||||
expect(testObject).toHaveProperty('testString');
|
||||
expect(testObject).toHaveProperty('text');
|
||||
});
|
||||
|
||||
it('should have an output to match the snapshot', () => {
|
||||
plugin(mockAST, file);
|
||||
expect(file.data).toMatchSnapshot();
|
||||
});
|
||||
});
|
32
curriculum/challenge-md-parser/text-to-data.js
Normal file
32
curriculum/challenge-md-parser/text-to-data.js
Normal file
@ -0,0 +1,32 @@
|
||||
const visit = require('unist-util-visit');
|
||||
const toHTML = require('hast-util-to-html');
|
||||
|
||||
const sectionFilter = (
|
||||
{ type, tagName, properties: { id = '' } },
|
||||
sectionId
|
||||
) => {
|
||||
return type === 'element' && tagName === 'section' && id === sectionId;
|
||||
};
|
||||
|
||||
function textToData(sectionIds) {
|
||||
if (!sectionIds || !Array.isArray(sectionIds) || sectionIds.length <= 0) {
|
||||
throw new Error("textToData must have an array of section id's supplied");
|
||||
}
|
||||
function transformer(tree, file) {
|
||||
visit(tree, 'element', visitor);
|
||||
function visitor(node) {
|
||||
sectionIds.forEach(sectionId => {
|
||||
if (sectionFilter(node, sectionId)) {
|
||||
const textArray = toHTML(node);
|
||||
file.data = {
|
||||
...file.data,
|
||||
[sectionId]: textArray
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return transformer;
|
||||
}
|
||||
|
||||
module.exports = textToData;
|
72
curriculum/challenge-md-parser/text-to-data.test.js
Normal file
72
curriculum/challenge-md-parser/text-to-data.test.js
Normal file
@ -0,0 +1,72 @@
|
||||
/* global describe it expect */
|
||||
const mockAST = require('./fixtures/challenge-html-ast.json');
|
||||
const textToData = require('./text-to-data');
|
||||
|
||||
describe('text-to-data', () => {
|
||||
const expectedField = 'description';
|
||||
const otherExpectedField = 'instructions';
|
||||
const unexpectedField = 'does-not-exis';
|
||||
let file = { data: {} };
|
||||
|
||||
beforeEach(() => {
|
||||
file = { data: {} };
|
||||
});
|
||||
|
||||
it('should take return a function', () => {
|
||||
const plugin = textToData(['a-section-id']);
|
||||
|
||||
expect(typeof plugin).toEqual('function');
|
||||
});
|
||||
|
||||
it('throws when no argument or the incorrect argument is supplied', () => {
|
||||
expect.assertions(5);
|
||||
const expectedError =
|
||||
"textToData must have an array of section id's supplied";
|
||||
expect(() => {
|
||||
textToData();
|
||||
}).toThrow(expectedError);
|
||||
expect(() => {
|
||||
textToData('');
|
||||
}).toThrow(expectedError);
|
||||
expect(() => {
|
||||
textToData({});
|
||||
}).toThrow(expectedError);
|
||||
expect(() => {
|
||||
textToData(1);
|
||||
}).toThrow(expectedError);
|
||||
expect(() => {
|
||||
textToData([]);
|
||||
}).toThrow(expectedError);
|
||||
});
|
||||
|
||||
it("should only add a value for 'found' section id's", () => {
|
||||
const plugin = textToData([expectedField, unexpectedField]);
|
||||
plugin(mockAST, file);
|
||||
expect(expectedField in file.data && !(unexpectedField in file.data)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a string relating to the section id to `file.data`', () => {
|
||||
const plugin = textToData([expectedField]);
|
||||
plugin(mockAST, file);
|
||||
const expectedText = 'Welcome to freeCodeCamp';
|
||||
expect(file.data[expectedField].includes(expectedText)).toBe(true);
|
||||
});
|
||||
|
||||
it('should preserve nested html', () => {
|
||||
const plugin = textToData([expectedField]);
|
||||
plugin(mockAST, file);
|
||||
const expectedText = `<blockquote>
|
||||
<p>Some text in a blockquote</p>
|
||||
<p>Some text in a blockquote, with <code>code</code></p>
|
||||
</blockquote>`;
|
||||
expect(file.data[expectedField].includes(expectedText)).toBe(true);
|
||||
});
|
||||
|
||||
it('should have an output to match the snapshot', () => {
|
||||
const plugin = textToData([expectedField, otherExpectedField]);
|
||||
plugin(mockAST, file);
|
||||
expect(file.data).toMatchSnapshot();
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user