Skip to content

Commit f11a7ab

Browse files
committed
refactor step tests
Signed-off-by: shmck <[email protected]>
1 parent 1e13c24 commit f11a7ab

File tree

3 files changed

+148
-98
lines changed

3 files changed

+148
-98
lines changed

src/templates/coderoad.yaml

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ config:
3131
# - npm install
3232
## App versions helps to ensure compatability with the Extension
3333
appVersions:
34-
{}
3534
## Ensure compatability with a minimal VSCode CodeRoad version
36-
# vscode: '>=0.7.0'
35+
vscode: ">=0.7.0"
3736
## Repo information to load code from
3837
##
3938
repo:
@@ -62,25 +61,16 @@ levels:
6261
## Setup for the first task. Required.
6362
setup:
6463
## Files to open in a text editor when the task loads. Optional.
65-
files: []
66-
# - package.json
67-
## Commits to load when the task loads. These should include failing tests. Required.
68-
## The list will be filled by the parser
69-
commits:
70-
[]
71-
# - a commit hash
64+
files:
65+
- package.json
7266
## Solution for the first task. Required.
7367
solution:
7468
## Files to open when the solution loads. Optional.
75-
files: []
76-
# - package.json
77-
## Commits that complete the task. All tests should pass when the commits load. These commits will not be loaded by the tutorial user in normal tutorial activity.
78-
## The list will be filled by the parser
79-
commits: []
69+
files:
70+
- package.json
8071
## Example Two: Running commands
8172
- id: L1S2
8273
setup:
83-
commits: []
8474
## CLI commands that are run when the task loads. Optional.
8575
commands:
8676
- npm install
@@ -94,27 +84,18 @@ levels:
9484
setup:
9585
files:
9686
- package.json
97-
commits:
98-
- commit7
9987
## Listeners that run tests when a file or directory changes.
10088
watchers:
10189
- package.json
10290
- node_modules/some-package
10391
solution:
10492
files:
10593
- package.json
106-
commits:
107-
- commit8
10894
## Example Four: Subtasks
10995
- id: L1S4
11096
setup:
111-
commits:
112-
- commit8
11397
commands:
11498
## A filter is a regex that limits the test results
11599
- filter: "^Example 2"
116100
## A feature that shows subtasks: all filtered active test names and the status of the tests (pass/fail).
117101
- subtasks: true
118-
solution:
119-
commits:
120-
- commit9

src/utils/parse.ts

Lines changed: 75 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import * as T from "../../typings/tutorial";
44

55
type TutorialFrame = {
66
summary: T.TutorialSummary;
7+
levels: {
8+
[levelKey: string]: T.Level;
9+
};
10+
steps: { [stepKey: string]: Partial<T.Step> };
711
};
812

913
export function parseMdContent(md: string): TutorialFrame | never {
@@ -24,73 +28,66 @@ export function parseMdContent(md: string): TutorialFrame | never {
2428
}
2529
});
2630

27-
const sections = {};
31+
const mdContent: TutorialFrame = {
32+
summary: {
33+
title: "",
34+
description: "",
35+
},
36+
levels: {},
37+
steps: {},
38+
};
2839

29-
// Identify and remove the header
40+
// Capture summary
3041
const summaryMatch = parts
3142
.shift()
3243
.match(/^#\s(?<tutorialTitle>.*)[\n\r]+(?<tutorialDescription>[^]*)/);
33-
3444
if (!summaryMatch.groups.tutorialTitle) {
3545
throw new Error("Missing tutorial title");
3646
}
47+
mdContent.summary.title = summaryMatch.groups.tutorialTitle.trim();
3748

3849
if (!summaryMatch.groups.tutorialDescription) {
3950
throw new Error("Missing tutorial summary description");
4051
}
41-
42-
sections["summary"] = {
43-
title: summaryMatch.groups.tutorialTitle.trim(),
44-
description: summaryMatch.groups.tutorialDescription.trim(),
45-
};
52+
mdContent.summary.description = summaryMatch.groups.tutorialDescription.trim();
4653

4754
// Identify each part of the content
48-
parts.forEach((section) => {
55+
parts.forEach((section: string) => {
56+
// match level
4957
const levelRegex = /^(##\s(?<levelId>L\d+)\s(?<levelTitle>.*)[\n\r]*(>\s*(?<levelSummary>.*))?[\n\r]+(?<levelContent>[^]*))/;
50-
const stepRegex = /^(###\s(?<stepId>(?<levelId>L\d+)S\d+)\s(?<stepTitle>.*)[\n\r]+(?<stepContent>[^]*))/;
51-
52-
const levelMatch = section.match(levelRegex);
53-
const stepMatch = section.match(stepRegex);
54-
55-
if (levelMatch) {
58+
const levelMatch: RegExpMatchArray | null = section.match(levelRegex);
59+
if (levelMatch && levelMatch.groups) {
5660
const {
5761
levelId,
5862
levelTitle,
5963
levelSummary,
6064
levelContent,
6165
} = levelMatch.groups;
6266

63-
const level = {
64-
[levelId]: {
65-
id: levelId,
66-
title: levelTitle,
67-
summary: levelSummary
68-
? levelSummary.trim()
69-
: _.truncate(levelContent, { length: 80, omission: "..." }),
70-
content: levelContent.trim(),
71-
},
72-
};
73-
74-
_.merge(sections, level);
75-
} else if (stepMatch) {
76-
const step = {
77-
[stepMatch.groups.levelId]: {
78-
steps: {
79-
[stepMatch.groups.stepId]: {
80-
id: stepMatch.groups.stepId,
81-
// title: stepMatch.groups.stepTitle, //Not using at this momemnt
82-
content: stepMatch.groups.stepContent.trim(),
83-
},
84-
},
85-
},
67+
// @ts-ignore
68+
mdContent.levels[levelId] = {
69+
id: levelId,
70+
title: levelTitle,
71+
summary: levelSummary
72+
? levelSummary.trim()
73+
: _.truncate(levelContent, { length: 80, omission: "..." }),
74+
content: levelContent.trim(),
8675
};
87-
88-
_.merge(sections, step);
76+
} else {
77+
// match step
78+
const stepRegex = /^(###\s(?<stepId>(?<levelId>L\d+)S\d+)\s(?<stepTitle>.*)[\n\r]+(?<stepContent>[^]*))/;
79+
const stepMatch: RegExpMatchArray | null = section.match(stepRegex);
80+
if (stepMatch && stepMatch.groups) {
81+
const { stepId, stepContent } = stepMatch.groups;
82+
mdContent.steps[stepId] = {
83+
id: stepId,
84+
content: stepContent.trim(),
85+
};
86+
}
8987
}
9088
});
9189

92-
// @ts-ignore
93-
return sections;
90+
return mdContent;
9491
}
9592

9693
type ParseParams = {
@@ -100,41 +97,41 @@ type ParseParams = {
10097
};
10198

10299
export function parse(params: ParseParams): any {
103-
const parsed = { ...params.config };
104-
105100
const mdContent: TutorialFrame = parseMdContent(params.text);
106101

107-
// Add the summary to the tutorial file
108-
parsed["summary"] = mdContent.summary;
102+
const parsed: Partial<T.Tutorial> = {
103+
summary: mdContent.summary,
104+
config: params.config.config,
105+
levels: [],
106+
};
109107

110108
// merge content and tutorial
111-
if (parsed.levels) {
112-
parsed.levels.forEach((level: T.Level, levelIndex: number) => {
113-
const levelContent = mdContent[level.id];
109+
if (params.config.levels && params.config.levels.length) {
110+
parsed.levels = params.config.levels.map(
111+
(level: T.Level, levelIndex: number) => {
112+
const levelContent = mdContent.levels[level.id];
113+
114+
if (!levelContent) {
115+
console.log(`Markdown content not found for ${level.id}`);
116+
return;
117+
}
114118

115-
if (!levelContent) {
116-
console.log(`Markdown content not found for ${level.id}`);
117-
return;
118-
}
119+
level = { ...level, ...levelContent };
119120

120-
// add level setup commits
121-
const levelSetupKey = `L${levelIndex + 1}`;
122-
if (params.commits[levelSetupKey]) {
123-
if (!level.setup) {
124-
level.setup = {
125-
commits: [],
126-
};
121+
// add level setup commits
122+
const levelSetupKey = level.id;
123+
if (params.commits[levelSetupKey]) {
124+
if (!level.setup) {
125+
level.setup = {
126+
commits: [],
127+
};
128+
}
129+
level.setup.commits = params.commits[levelSetupKey];
127130
}
128-
level.setup.commits = params.commits[levelSetupKey];
129-
}
130-
131-
const { steps, ...content } = levelContent;
132131

133-
// add level step commits
134-
if (steps) {
135-
level.steps = Object.keys(steps).map(
136-
(stepId: string, stepIndex: number) => {
137-
const step: T.Step = steps[stepId];
132+
// add level step commits
133+
level.steps = (level.steps || []).map(
134+
(step: T.Step, stepIndex: number) => {
138135
const stepKey = `${levelSetupKey}S${stepIndex + 1}`;
139136
const stepSetupKey = `${stepKey}Q`;
140137
if (params.commits[stepSetupKey]) {
@@ -156,16 +153,20 @@ export function parse(params: ParseParams): any {
156153
step.solution.commits = params.commits[stepSolutionKey];
157154
}
158155

156+
// add markdown
157+
const stepMarkdown: Partial<T.Step> = mdContent.steps[step.id];
158+
if (stepMarkdown) {
159+
step = { ...step, ...stepMarkdown };
160+
}
161+
159162
step.id = `${stepKey}`;
160163
return step;
161164
}
162165
);
163-
} else {
164-
level.steps = [];
165-
}
166166

167-
_.merge(level, content);
168-
});
167+
return level;
168+
}
169+
);
169170
}
170171

171172
return parsed;

tests/parse.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,74 @@ The first step
359359
expect(result.levels[0].setup).toEqual(expected.levels[0].setup);
360360
});
361361

362+
it("should load the full config for a step", () => {
363+
const md = `# Title
364+
365+
Description.
366+
367+
## L1 Title
368+
369+
First line
370+
371+
### L1S1 Step
372+
373+
The first step
374+
`;
375+
const config = {
376+
levels: [
377+
{
378+
id: "L1",
379+
steps: [
380+
{
381+
id: "L1S1",
382+
setup: {
383+
commands: ["npm install"],
384+
files: ["someFile.js"],
385+
watchers: ["someFile.js"],
386+
filter: "someFilter",
387+
subtasks: true,
388+
},
389+
},
390+
],
391+
},
392+
],
393+
};
394+
const result = parse({
395+
text: md,
396+
config,
397+
commits: {
398+
L1S1Q: ["abcdefg1", "123456789"],
399+
},
400+
});
401+
const expected = {
402+
summary: {
403+
description: "Description.",
404+
},
405+
levels: [
406+
{
407+
id: "L1",
408+
summary: "First line",
409+
content: "First line",
410+
steps: [
411+
{
412+
id: "L1S1",
413+
content: "The first step",
414+
setup: {
415+
commits: ["abcdefg1", "123456789"],
416+
commands: ["npm install"],
417+
files: ["someFile.js"],
418+
watchers: ["someFile.js"],
419+
filter: "someFilter",
420+
subtasks: true,
421+
},
422+
},
423+
],
424+
},
425+
],
426+
};
427+
expect(result.levels[0].steps[0]).toEqual(expected.levels[0].steps[0]);
428+
});
429+
362430
// config
363431
it("should parse the tutorial config", () => {
364432
const md = `# Title

0 commit comments

Comments
 (0)