From 504103a41aa5e82525c2e0ad7ab4f1fb7e00b4cd Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 18:42:44 -0700 Subject: [PATCH 01/17] setup tests Signed-off-by: shmck --- tests/commitOrder.test.ts | 30 +++---- tests/markdown.test.ts | 16 ++-- tests/parse.test.ts | 178 +++++++++++++++++++------------------- tests/skeleton.test.ts | 10 +-- tests/tutorial.test.ts | 2 +- 5 files changed, 118 insertions(+), 118 deletions(-) diff --git a/tests/commitOrder.test.ts b/tests/commitOrder.test.ts index 38e1035..2c5dedd 100644 --- a/tests/commitOrder.test.ts +++ b/tests/commitOrder.test.ts @@ -2,7 +2,7 @@ import { validateCommitOrder } from "../src/utils/validateCommits"; describe("commitOrder", () => { it("should return true if order is valid", () => { - const positions = ["INIT", "L1", "L1S1", "L1S2", "L2", "L2S1"]; + const positions = ["INIT", "1", "1.1", "1.2", "2", "2.1"]; const result = validateCommitOrder(positions); expect(result).toBe(true); }); @@ -10,37 +10,37 @@ describe("commitOrder", () => { const positions = [ "INIT", "INIT", - "L1", - "L1", - "L1S1", - "L1S1", - "L1S2", - "L1S2", - "L2", - "L2", - "L2S1", - "L2S1", + "1", + "1", + "1.1", + "1.1", + "1.2", + "1.2", + "2", + "2", + "2.1", + "2.1", ]; const result = validateCommitOrder(positions); expect(result).toBe(true); }); it("should return false if INIT is out of order", () => { - const positions = ["INIT", "L1", "L1S1", "L1S2", "INIT", "L2", "L2S1"]; + const positions = ["INIT", "1", "1.1", "1.2", "INIT", "2", "2.1"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); it("should return false if level after step is out of order", () => { - const positions = ["INIT", "L1", "L1S1", "L1S2", "L2S1", "L2"]; + const positions = ["INIT", "1", "1.1", "1.2", "2.1", "2"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); it("should return false if level is out of order", () => { - const positions = ["INIT", "L1", "L3", "L2"]; + const positions = ["INIT", "1", "L3", "2"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); it("should return false if step is out of order", () => { - const positions = ["INIT", "L1", "L1S1", "L1S3", "L1S2"]; + const positions = ["INIT", "1", "1.1", "1.3", "1.2"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts index 9b90028..8eb78d3 100644 --- a/tests/markdown.test.ts +++ b/tests/markdown.test.ts @@ -5,7 +5,7 @@ describe("validate markdown", () => { const md = ` Description. -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -19,7 +19,7 @@ Description. # Another Title -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -29,7 +29,7 @@ Some text that describes the level`; Description. -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -45,7 +45,7 @@ Some text that describes the level it("should return false if missing a summary description", () => { const md = `# A Title -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -90,13 +90,13 @@ First step Description. -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. Some text that describes the level -### L1S1 +### Step 1 First Step`; expect(validateMarkdown(md)).toBe(true); @@ -114,7 +114,7 @@ Should not be a problem \`\`\` -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -126,7 +126,7 @@ Some text that describes the level Should not be an issue \`\`\` -### L1S1 +### Step 1 First Step`; expect(validateMarkdown(md)).toBe(true); diff --git a/tests/parse.test.ts b/tests/parse.test.ts index c4d6ca9..1f6e2e7 100644 --- a/tests/parse.test.ts +++ b/tests/parse.test.ts @@ -32,7 +32,7 @@ Short description to be shown as a tutorial's subtitle. Description. -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -51,7 +51,7 @@ Some text const expected = { levels: [ { - id: "L1", + id: "1", title: "Put Level's title here", summary: "Level's summary: a short description of the level's content in one line.", @@ -68,7 +68,7 @@ Some text Description. -## L1 Put Level's title here +## Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -78,7 +78,7 @@ Some text const skeleton = { levels: [ { - id: "L1", + id: "1", setup: { files: [], commits: [] }, solution: { files: [], commits: [] }, steps: [], @@ -93,7 +93,7 @@ Some text const expected = { levels: [ { - id: "L1", + id: "1", title: "Put Level's title here", summary: "Level's summary: a short description of the level's content in one line.", @@ -112,12 +112,12 @@ Some text Description. -## L1 Put Level's title here +## Put Level's title here Some text that becomes the summary `; - const skeleton = { levels: [{ id: "L1" }] }; + const skeleton = { levels: [{ id: "1" }] }; const result = parse({ text: md, skeleton, @@ -126,7 +126,7 @@ Some text that becomes the summary const expected = { levels: [ { - id: "L1", + id: "1", title: "Put Level's title here", summary: "Some text that becomes the summary", content: "Some text that becomes the summary", @@ -142,12 +142,12 @@ Some text that becomes the summary Description. -## L1 Put Level's title here +## Put Level's title here Some text that becomes the summary and goes beyond the maximum length of 80 so that it gets truncated at the end `; - const skeleton = { levels: [{ id: "L1" }] }; + const skeleton = { levels: [{ id: "1" }] }; const result = parse({ text: md, skeleton, @@ -156,7 +156,7 @@ Some text that becomes the summary and goes beyond the maximum length of 80 so t const expected = { levels: [ { - id: "L1", + id: "1", title: "Put Level's title here", summary: "Some text that becomes the summary", content: "Some text that becomes the summary", @@ -171,14 +171,14 @@ Some text that becomes the summary and goes beyond the maximum length of 80 so t Description. -## L1 Put Level's title here +## 1 Put Level's title here Some text. But not including this line. `; - const skeleton = { levels: [{ id: "L1" }] }; + const skeleton = { levels: [{ id: "1" }] }; const result = parse({ text: md, skeleton, @@ -187,7 +187,7 @@ But not including this line. const expected = { levels: [ { - id: "L1", + id: "1", title: "Put Level's title here", summary: "Some text.", content: "Some text.\n\nBut not including this line.", @@ -203,7 +203,7 @@ But not including this line. Description. -## L1 Put Level's title here +## Put Level's title here > @@ -212,7 +212,7 @@ Some text. But not including this line. `; - const skeleton = { levels: [{ id: "L1" }] }; + const skeleton = { levels: [{ id: "1" }] }; const result = parse({ text: md, skeleton, @@ -221,7 +221,7 @@ But not including this line. const expected = { levels: [ { - id: "L1", + id: "1", title: "Put Level's title here", summary: "Some text.", content: "Some text.\n\nBut not including this line.", @@ -239,7 +239,7 @@ Description. Second description line -## L1 Titles +## Titles First line @@ -248,7 +248,7 @@ Second line Third line `; - const skeleton = { levels: [{ id: "L1" }] }; + const skeleton = { levels: [{ id: "1" }] }; const result = parse({ text: md, skeleton, @@ -260,7 +260,7 @@ Third line }, levels: [ { - id: "L1", + id: "1", summary: "Some text that becomes the summary", content: "First line\n\nSecond line\n\nThird line", }, @@ -275,21 +275,21 @@ Third line Description. -## L1 Title +## Title First line -### L1S1 Step +### 1.1 The first step `; const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, ], }, @@ -308,12 +308,12 @@ The first step }, levels: [ { - id: "L1", + id: "1", summary: "First line", content: "First line", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdefg1"], @@ -331,21 +331,21 @@ The first step Description. -## L1 Title +## Title First line -### L1S1 Step +### 1.1 Step The first step `; const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, ], }, @@ -364,12 +364,12 @@ The first step }, levels: [ { - id: "L1", + id: "1", summary: "First line", content: "First line", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdefg1", "123456789"], @@ -387,18 +387,18 @@ The first step Description. -## L1 Title +## Title First line -### L1S1 +### 1.1 The first step `; const skeleton = { levels: [ { - id: "L1", + id: "1", }, ], }; @@ -415,7 +415,7 @@ The first step }, levels: [ { - id: "L1", + id: "1", summary: "First line", content: "First line", setup: { @@ -432,11 +432,11 @@ The first step Description. -## L1 Title +## 1. Title First line -### L1S1 +### 1.1 The first step @@ -451,10 +451,10 @@ Another line const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, ], }, @@ -469,7 +469,7 @@ Another line }, }); const expected = { - id: "L1S1", + id: "1.1", setup: { commits: ["12345678"], }, @@ -484,21 +484,21 @@ Another line Description. -## L1 Title +## 1. Title First line -### L1S1 Step +### 1.1 Step The first step `; const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", setup: { commands: ["npm install"], files: ["someFile.js"], @@ -529,12 +529,12 @@ The first step }, levels: [ { - id: "L1", + id: "1", summary: "First line", content: "First line", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdefg1", "123456789"], @@ -562,33 +562,33 @@ The first step Description. -## L1 Title 1 +## 1. Title 1 First level content. -### L1S1 +### 1.1 The first step -### L1S2 +### 1.2 The second step -## L2 Title 2 +## 2. Title 2 Second level content. -### L2S1 +### 2.1 The third step `; const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", setup: { commands: ["npm install"], files: ["someFile.js"], @@ -602,7 +602,7 @@ The third step }, }, { - id: "L1S2", + id: "1.2", setup: { commands: ["npm install"], files: ["someFile.js"], @@ -618,12 +618,12 @@ The third step ], }, { - id: "L2", + id: "2", summary: "Second level content.", content: "First level content.", steps: [ { - id: "L2S1", + id: "2.1", setup: { commands: ["npm install"], files: ["someFile.js"], @@ -658,13 +658,13 @@ The third step }, levels: [ { - id: "L1", + id: "1", title: "Title 1", summary: "First level content.", content: "First level content.", steps: [ { - id: "L1S1", + id: "L11.1S1", content: "The first step", setup: { commits: ["abcdef1", "123456789"], @@ -681,7 +681,7 @@ The third step }, }, { - id: "L1S2", + id: "1.2", content: "The second step", setup: { commits: ["2abcdef"], @@ -700,13 +700,13 @@ The third step ], }, { - id: "L2", + id: "2", title: "Title 2", summary: "Second level content.", content: "Second level content.", steps: [ { - id: "L2S1", + id: "2.1", content: "The third step", setup: { commits: ["4abcdef"], @@ -734,11 +734,11 @@ The third step Description. -## L1 Title 1 +## 1. Title 1 First level content. -### L1S1 +### 1.1 The first step @@ -746,10 +746,10 @@ The first step const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, ], }, @@ -768,13 +768,13 @@ The first step }, levels: [ { - id: "L1", + id: "1", title: "Title 1", summary: "First level content.", content: "First level content.", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdef1", "123456789"], @@ -941,11 +941,11 @@ Description. Description. -## L1 Title 1 +## 1. Title 1 First level content. -### L1S1 +### 1.1 The first step @@ -958,10 +958,10 @@ The first step const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, ], }, @@ -980,13 +980,13 @@ The first step }, levels: [ { - id: "L1", + id: "1", title: "Title 1", summary: "First level content.", content: "First level content.", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdef1", "123456789"], @@ -1005,11 +1005,11 @@ The first step Description. -## L1 Title 1 +## 1. Title 1 First level content. -### L1S1 +### 1.1 The first step @@ -1027,10 +1027,10 @@ And spans multiple lines. const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, ], }, @@ -1049,13 +1049,13 @@ And spans multiple lines. }, levels: [ { - id: "L1", + id: "1", title: "Title 1", summary: "First level content.", content: "First level content.", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdef1", "123456789"], @@ -1077,11 +1077,11 @@ And spans multiple lines. Description. -## L1 Title 1 +## 1. Title 1 First level content. -### L1S1 +### 1.1 The first step @@ -1096,20 +1096,20 @@ var a = 1; And spans multiple lines. -### L1S2 +### 1.2 The second uninterrupted step `; const skeleton = { levels: [ { - id: "L1", + id: "1", steps: [ { - id: "L1S1", + id: "1.1", }, { - id: "L1S2", + id: "1.2", }, ], }, @@ -1130,13 +1130,13 @@ The second uninterrupted step }, levels: [ { - id: "L1", + id: "1", title: "Title 1", summary: "First level content.", content: "First level content.", steps: [ { - id: "L1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdef1"], @@ -1150,7 +1150,7 @@ The second uninterrupted step ], }, { - id: "L1S2", + id: "1.2", content: "The second uninterrupted step", setup: { commits: ["fedcba1"], diff --git a/tests/skeleton.test.ts b/tests/skeleton.test.ts index 4025bc8..29cbe9a 100644 --- a/tests/skeleton.test.ts +++ b/tests/skeleton.test.ts @@ -30,7 +30,7 @@ const validJson = { { steps: [ { - id: "L1S1", + id: "1.1", setup: { files: ["package.json"], }, @@ -39,7 +39,7 @@ const validJson = { }, }, { - id: "L1S2", + id: "1.2", setup: { commands: ["npm install"], }, @@ -48,7 +48,7 @@ const validJson = { }, }, { - id: "L1S3", + id: "1.3", setup: { files: ["package.json"], watchers: ["package.json", "node_modules/some-package"], @@ -58,7 +58,7 @@ const validJson = { }, }, { - id: "L1S4", + id: "1.4", setup: { commands: [], filter: "^Example 2", @@ -66,7 +66,7 @@ const validJson = { }, }, ], - id: "L1", + id: "1", }, ], }; diff --git a/tests/tutorial.test.ts b/tests/tutorial.test.ts index 2f7dde7..f0e809c 100644 --- a/tests/tutorial.test.ts +++ b/tests/tutorial.test.ts @@ -39,7 +39,7 @@ describe("validate tutorial", () => { }, levels: [ { - id: "L1", + id: "1", title: "Level 1", summary: "The first level", content: "The first level", From 8ba48775ffa5c548f68426146abf0c01a0f062da Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 18:54:40 -0700 Subject: [PATCH 02/17] update markdown validation Signed-off-by: shmck --- src/utils/validateMarkdown.ts | 14 +++++++------- tests/markdown.test.ts | 21 +++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/utils/validateMarkdown.ts b/src/utils/validateMarkdown.ts index c4d13bb..9933c67 100644 --- a/src/utils/validateMarkdown.ts +++ b/src/utils/validateMarkdown.ts @@ -24,11 +24,11 @@ const validations: Validation[] = [ }, }, { - message: "should have a level `##` with a format of `L[0-9]+`", + message: "should have a level `##` with a format of `[0-9]+.`", validate: (t) => { const headers = t.match(/^#{2}\s(.+)$/gm) || []; for (const header of headers) { - if (!header.match(/^#{2}\s(L\d+)\s(.+)$/)) { + if (!header.match(/^#{2}\s(\d+\.)\s(.+)$/)) { return false; } } @@ -36,11 +36,11 @@ const validations: Validation[] = [ }, }, { - message: "should have a step `###` with a format of `L[0-9]+S[0-9]+`", + message: "should have a step `###` with a format of `[0-9].[0-9]+`", validate: (t) => { const headers = t.match(/^#{3}\s(.+)$/gm) || []; for (const header of headers) { - if (!header.match(/^#{3}\s(L\d+)S\d+/)) { + if (!header.match(/^#{3}\s(\d+\.\d+)/)) { return false; } } @@ -60,9 +60,9 @@ export function validateMarkdown(md: string): boolean { for (const v of validations) { if (!v.validate(text)) { valid = false; - if (process.env.NODE_ENV !== "test") { - console.warn(v.message); - } + // if (process.env.NODE_ENV !== "test") { + console.warn(v.message); + // } } } diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts index 8eb78d3..0ab15bf 100644 --- a/tests/markdown.test.ts +++ b/tests/markdown.test.ts @@ -5,7 +5,7 @@ describe("validate markdown", () => { const md = ` Description. -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -19,7 +19,7 @@ Description. # Another Title -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -29,7 +29,7 @@ Some text that describes the level`; Description. -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -45,7 +45,7 @@ Some text that describes the level it("should return false if missing a summary description", () => { const md = `# A Title -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -79,10 +79,11 @@ A description Some text that describes the level -### A Step +### Missing step id First step `; + expect(validateMarkdown(md)).toBe(false); }); it("should return true for valid markdown", () => { @@ -90,13 +91,13 @@ First step Description. -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. Some text that describes the level -### Step 1 +### 1.1 First Step`; expect(validateMarkdown(md)).toBe(true); @@ -114,19 +115,19 @@ Should not be a problem \`\`\` -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. Some text that describes the level \`\`\` -## Another Level in markdown +## 2. Another Level in markdown Should not be an issue \`\`\` -### Step 1 +### 1.1 First Step`; expect(validateMarkdown(md)).toBe(true); From 67bdfd7e6291d7d41a0432680993b4a154c43263 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 19:01:39 -0700 Subject: [PATCH 03/17] test/update commit order Signed-off-by: shmck --- src/utils/validateCommits.ts | 4 +- tests/commitOrder.test.ts | 130 ++++++++++++++++++++++++----------- 2 files changed, 91 insertions(+), 43 deletions(-) diff --git a/src/utils/validateCommits.ts b/src/utils/validateCommits.ts index 80d714d..68cfaa4 100644 --- a/src/utils/validateCommits.ts +++ b/src/utils/validateCommits.ts @@ -13,8 +13,8 @@ export function validateCommitOrder(positions: string[]): boolean { current = { level: 0, step: 0 }; return; } else { - const levelMatch = position.match(/^L([0-9]+)Q?$/); - const stepMatch = position.match(/^L([0-9]+)S([0-9]+)[Q|A]?$/); + const levelMatch = position.match(/^L?([0-9]+)Q?$/); + const stepMatch = position.match(/^L?([0-9]+)[S|\.]([0-9]+)[Q|A]?$/); if (levelMatch) { // allows next level or step const [_, levelString] = levelMatch; diff --git a/tests/commitOrder.test.ts b/tests/commitOrder.test.ts index 2c5dedd..478b433 100644 --- a/tests/commitOrder.test.ts +++ b/tests/commitOrder.test.ts @@ -1,47 +1,95 @@ import { validateCommitOrder } from "../src/utils/validateCommits"; describe("commitOrder", () => { - it("should return true if order is valid", () => { - const positions = ["INIT", "1", "1.1", "1.2", "2", "2.1"]; - const result = validateCommitOrder(positions); - expect(result).toBe(true); + describe("#.# format", () => { + it("should return true if order is valid", () => { + const positions = ["INIT", "1", "1.1", "1.2", "2", "2.1"]; + const result = validateCommitOrder(positions); + expect(result).toBe(true); + }); + it("should return true if valid with duplicates", () => { + const positions = [ + "INIT", + "INIT", + "1", + "1", + "1.1", + "1.1", + "1.2", + "1.2", + "2", + "2", + "2.1", + "2.1", + ]; + const result = validateCommitOrder(positions); + expect(result).toBe(true); + }); + it("should return false if INIT is out of order", () => { + const positions = ["INIT", "1", "1.1", "1.2", "INIT", "2", "2.1"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if level after step is out of order", () => { + const positions = ["INIT", "1", "1.1", "1.2", "2.1", "2"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if level is out of order", () => { + const positions = ["INIT", "1", "L3", "2"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if step is out of order", () => { + const positions = ["INIT", "1", "1.1", "1.3", "1.2"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); }); - it("should return true if valid with duplicates", () => { - const positions = [ - "INIT", - "INIT", - "1", - "1", - "1.1", - "1.1", - "1.2", - "1.2", - "2", - "2", - "2.1", - "2.1", - ]; - const result = validateCommitOrder(positions); - expect(result).toBe(true); - }); - it("should return false if INIT is out of order", () => { - const positions = ["INIT", "1", "1.1", "1.2", "INIT", "2", "2.1"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - it("should return false if level after step is out of order", () => { - const positions = ["INIT", "1", "1.1", "1.2", "2.1", "2"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - it("should return false if level is out of order", () => { - const positions = ["INIT", "1", "L3", "2"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - it("should return false if step is out of order", () => { - const positions = ["INIT", "1", "1.1", "1.3", "1.2"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); + // @deprecated + describe("L#S# format", () => { + it("should return true if order is valid", () => { + const positions = ["INIT", "L1", "L1S1", "L1S2", "L2", "L2S1"]; + const result = validateCommitOrder(positions); + expect(result).toBe(true); + }); + it("should return true if valid with duplicates", () => { + const positions = [ + "INIT", + "INIT", + "L1", + "L1", + "L1S1", + "L1S1", + "L1S2", + "L1S2", + "L2", + "L2", + "L2S1", + "L2S1", + ]; + const result = validateCommitOrder(positions); + expect(result).toBe(true); + }); + it("should return false if INIT is out of order", () => { + const positions = ["INIT", "L1", "L1S1", "L1S2", "INIT", "L2", "L2S1"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if level after step is out of order", () => { + const positions = ["INIT", "L1", "L1S1", "L1S2", "L2S1", "L2"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if level is out of order", () => { + const positions = ["INIT", "L1", "L3", "L2"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if step is out of order", () => { + const positions = ["INIT", "L1", "L1S1", "L1S3", "L1S2"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); }); }); From 8af3bf6a683e08f8c2e1cc96e8a9f221aa140cf9 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 19:48:34 -0700 Subject: [PATCH 04/17] update parser progress Signed-off-by: shmck --- src/utils/parse.ts | 170 +++++++++++++++++++++--------------------- tests/parse.test.ts | 22 +++--- typings/tutorial.d.ts | 2 +- 3 files changed, 97 insertions(+), 97 deletions(-) diff --git a/src/utils/parse.ts b/src/utils/parse.ts index f8bf49d..2e3d146 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -4,10 +4,7 @@ import * as T from "../../typings/tutorial"; type TutorialFrame = { summary: T.TutorialSummary; - levels: { - [levelKey: string]: T.Level; - }; - steps: { [stepKey: string]: Partial }; + levels: T.Level[]; }; export function parseMdContent(md: string): TutorialFrame | never { @@ -33,8 +30,7 @@ export function parseMdContent(md: string): TutorialFrame | never { title: "", description: "", }, - levels: {}, - steps: {}, + levels: [], }; // Capture summary @@ -49,23 +45,20 @@ export function parseMdContent(md: string): TutorialFrame | never { mdContent.summary.description = summaryMatch.groups.tutorialDescription.trim(); } - let current = { level: "0", step: "0" }; + let current = { level: -1, step: 0 }; // Identify each part of the content parts.forEach((section: string) => { // match level - const levelRegex = /^(#{2}\s(?L\d+)\s(?.*)[\n\r]*(>\s(?.*))?[\n\r]+(?[^]*))/; + const levelRegex = /^(#{2}\s(?L?\d+\.?)\s(?.*)[\n\r]*(>\s(?.*))?[\n\r]+(?[^]*))/; const levelMatch: RegExpMatchArray | null = section.match(levelRegex); + if (levelMatch && levelMatch.groups) { - const { - levelId, - levelTitle, - levelSummary, - levelContent, - } = levelMatch.groups; + current = { level: current.level + 1, step: 0 }; + const { levelTitle, levelSummary, levelContent } = levelMatch.groups; // @ts-ignore - mdContent.levels[levelId] = { - id: levelId, + mdContent.levels[current.level] = { + id: (current.level + 1).toString(), title: levelTitle.trim(), summary: levelSummary && levelSummary.trim().length @@ -75,20 +68,21 @@ export function parseMdContent(md: string): TutorialFrame | never { omission: "...", }), content: levelContent.trim(), + steps: [], }; - current = { level: levelId, step: "0" }; } else { // match step - const stepRegex = /^(#{3}\s(?(?L\d+)S\d+)\s(?.*)[\n\r]+(?[^]*))/; + const stepRegex = /^(#{3}\s\(?.*)[\n\r]+(?[^]*)/; const stepMatch: RegExpMatchArray | null = section.match(stepRegex); if (stepMatch && stepMatch.groups) { const { stepId, stepContent } = stepMatch.groups; - - mdContent.steps[stepId] = { + mdContent.levels[current.level].steps[current.step] = { id: stepId, content: stepContent.trim(), + setup: {}, + solution: {}, }; - current = { ...current, step: stepId }; + current = { ...current, step: current.step + 1 }; } else { // parse hints from stepContent const hintDetectRegex = /^(#{4}\sHINTS[\n\r]+(\*\s(?[^]*))[\n\r]+)+/; @@ -100,7 +94,7 @@ export function parseMdContent(md: string): TutorialFrame | never { .slice(1) // remove #### HINTS .map((h) => h.trim()); if (hints.length) { - mdContent.steps[current.step].hints = hints; + mdContent.levels[current.level].steps[current.step].hints = hints; } } } @@ -135,72 +129,78 @@ export function parse(params: ParseParams): any { }; } - // merge content and tutorial - if (params.skeleton.levels && params.skeleton.levels.length) { - parsed.levels = params.skeleton.levels - .map((level: T.Level, levelIndex: number) => { - const levelContent = mdContent.levels[level.id]; + // merge content levels and tutorial - if (!levelContent) { - return null; - } - - level = { ...level, ...levelContent }; - - // add level setup commits - const levelSetupKey = level.id; - if (params.commits[levelSetupKey]) { - level.setup = { - ...(level.setup || {}), - commits: params.commits[levelSetupKey], - }; - } + parsed.levels = mdContent.levels.map((level: T.Level, levelIndex: number) => { + // add level setup commits + const levelId = level.id; + if (params.commits[levelId]) { + if (!level.setup) { + level.setup = {}; + } + level.setup.commits = params.commits[levelId]; + } - // add level step commits - try { - level.steps = (level.steps || []).map( - (step: T.Step, stepIndex: number) => { - const stepKey = `${levelSetupKey}S${stepIndex + 1}`; - const stepSetupKey = `${stepKey}Q`; - if (params.commits[stepSetupKey]) { - if (!step.setup) { - step.setup = { - commits: [], - }; - } - step.setup.commits = params.commits[stepSetupKey]; - } - - const stepSolutionKey = `${stepKey}A`; - if (params.commits[stepSolutionKey]) { - if (!step.solution) { - step.solution = { - commits: [], - }; - } - step.solution.commits = params.commits[stepSolutionKey]; - } - - // add markdown - const stepMarkdown: Partial = mdContent.steps[step.id]; - if (stepMarkdown) { - step = { ...step, ...stepMarkdown }; - } - - step.id = `${stepKey}`; - return step; - } - ); - } catch (error) { - console.log(JSON.stringify(level.steps)); - console.error("Error parsing level steps"); - console.error(error.message); - } + // get yaml for level + const configLevel = params.skeleton.levels.find( + (l: Partial) => l.id === levelId + ); + + let configSteps = {}; + if (configLevel) { + const { steps, ...configLevelProps } = configLevel; + level = { ...configLevelProps, ...level }; + if (steps) { + steps.forEach((s: T.Step) => { + configSteps[s.id] = s; + }); + } + } - return level; - }) - .filter((l: T.Level | null) => !!l); - } + // add level step commits + // try { + // level.steps = (level.steps || []).map( + // (step: T.Step, stepIndex: number) => { + // const stepKey = `${levelId}S${stepIndex + 1}`; + // const stepSetupKey = `${stepKey}Q`; + // if (params.commits[stepSetupKey]) { + // if (!step.setup) { + // step.setup = { + // commits: [], + // }; + // } + // step.setup.commits = params.commits[stepSetupKey]; + // } + + // const stepSolutionKey = `${stepKey}A`; + // if (params.commits[stepSolutionKey]) { + // if (!step.solution) { + // step.solution = { + // commits: [], + // }; + // } + // step.solution.commits = params.commits[stepSolutionKey]; + // } + + // // add markdown + // const stepMarkdown: Partial = + // mdContent.levels[level.id].steps[step.id]; + // if (stepMarkdown) { + // step = { ...step, ...stepMarkdown }; + // } + + // step.id = `${stepKey}`; + // return step; + // } + // ); + // } catch (error) { + // console.log(JSON.stringify(level.steps)); + // console.error("Error parsing level steps"); + // console.error(error.message); + // } + + return level; + }); return parsed; } diff --git a/tests/parse.test.ts b/tests/parse.test.ts index 1f6e2e7..c83b6dc 100644 --- a/tests/parse.test.ts +++ b/tests/parse.test.ts @@ -32,7 +32,7 @@ Short description to be shown as a tutorial's subtitle. Description. -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -68,7 +68,7 @@ Some text Description. -## Put Level's title here +## 1. Put Level's title here > Level's summary: a short description of the level's content in one line. @@ -112,7 +112,7 @@ Some text Description. -## Put Level's title here +## 1. Put Level's title here Some text that becomes the summary `; @@ -142,7 +142,7 @@ Some text that becomes the summary Description. -## Put Level's title here +## 1. Put Level's title here Some text that becomes the summary and goes beyond the maximum length of 80 so that it gets truncated at the end `; @@ -171,7 +171,7 @@ Some text that becomes the summary and goes beyond the maximum length of 80 so t Description. -## 1 Put Level's title here +## 1. Put Level's title here Some text. @@ -203,7 +203,7 @@ But not including this line. Description. -## Put Level's title here +## 1. Put Level's title here > @@ -239,7 +239,7 @@ Description. Second description line -## Titles +## 1. Titles First line @@ -275,7 +275,7 @@ Third line Description. -## Title +## 1. Title First line @@ -331,7 +331,7 @@ The first step Description. -## Title +## 1. Title First line @@ -387,7 +387,7 @@ The first step Description. -## Title +## 1. Title First line @@ -935,7 +935,7 @@ Description. }); }); - describe("hints", () => { + xdescribe("hints", () => { it("should parse hints for a step", () => { const md = `# Title diff --git a/typings/tutorial.d.ts b/typings/tutorial.d.ts index 82b1e2e..5484d57 100644 --- a/typings/tutorial.d.ts +++ b/typings/tutorial.d.ts @@ -48,7 +48,7 @@ export type TutorialSummary = { export type StepActions = { commands?: string[]; - commits: string[]; + commits?: string[]; files?: string[]; watchers?: string[]; filter?: string; From 4bb28b9b0f4a426608d973c164aaafac7e61e4c4 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 20:53:49 -0700 Subject: [PATCH 05/17] passing step tests Signed-off-by: shmck --- src/utils/parse.ts | 169 ++++++++++++++++++++++++----------------- tests/parse.test.ts | 44 +++++------ tests/skeleton.test.ts | 2 +- typings/tutorial.d.ts | 4 +- 4 files changed, 124 insertions(+), 95 deletions(-) diff --git a/src/utils/parse.ts b/src/utils/parse.ts index 2e3d146..f5cfd46 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -72,15 +72,13 @@ export function parseMdContent(md: string): TutorialFrame | never { }; } else { // match step - const stepRegex = /^(#{3}\s\(?.*)[\n\r]+(?[^]*)/; + const stepRegex = /^(#{3}\s(?.*)[\n\r]+(?[^]*))/; const stepMatch: RegExpMatchArray | null = section.match(stepRegex); if (stepMatch && stepMatch.groups) { const { stepId, stepContent } = stepMatch.groups; mdContent.levels[current.level].steps[current.step] = { - id: stepId, + id: `${current.level + 1}.${current.step + 1}`, content: stepContent.trim(), - setup: {}, - solution: {}, }; current = { ...current, step: current.step + 1 }; } else { @@ -131,76 +129,107 @@ export function parse(params: ParseParams): any { // merge content levels and tutorial - parsed.levels = mdContent.levels.map((level: T.Level, levelIndex: number) => { - // add level setup commits - const levelId = level.id; - if (params.commits[levelId]) { - if (!level.setup) { - level.setup = {}; + parsed.levels = mdContent.levels.map( + (mdLevel: T.Level, mdLevelIndex: number) => { + // add level setup commits + let level: T.Level = { ...mdLevel }; + + const configLevel = params.skeleton.levels[mdLevelIndex]; + + if (configLevel) { + // add level step commits + const { steps, ...configLevelProps } = configLevel; + level = { ...configLevelProps, ...level }; + if (steps) { + steps.forEach((step: T.Step, index: number) => { + console.log("step", step); + const mdStep = level.steps[index]; + console.log("mdStep", mdStep); + step = { + ...step, + ...mdStep, + }; + + const stepKey = step.id; + console.log("stepKey", stepKey); + const stepSetupKey = `${stepKey}Q`; + if (params.commits[stepSetupKey]) { + if (!step.setup) { + step.setup = { + commits: [], + }; + } + step.setup.commits = params.commits[stepSetupKey]; + } + + const stepSolutionKey = `${stepKey}A`; + if (params.commits[stepSolutionKey]) { + if (!step.solution) { + step.solution = { + commits: [], + }; + } + step.solution.commits = params.commits[stepSolutionKey]; + } + // update level step + level.steps[index] = step; + }); + } } - level.setup.commits = params.commits[levelId]; - } - // get yaml for level - const configLevel = params.skeleton.levels.find( - (l: Partial) => l.id === levelId - ); - - let configSteps = {}; - if (configLevel) { - const { steps, ...configLevelProps } = configLevel; - level = { ...configLevelProps, ...level }; - if (steps) { - steps.forEach((s: T.Step) => { - configSteps[s.id] = s; - }); + // try { + // level.steps = (level.steps || []).map( + // (step: T.Step, stepIndex: number) => { + // const stepKey = `${levelId}S${stepIndex + 1}`; + // const stepSetupKey = `${stepKey}Q`; + // if (params.commits[stepSetupKey]) { + // if (!step.setup) { + // step.setup = { + // commits: [], + // }; + // } + // step.setup.commits = params.commits[stepSetupKey]; + // } + + // const stepSolutionKey = `${stepKey}A`; + // if (params.commits[stepSolutionKey]) { + // if (!step.solution) { + // step.solution = { + // commits: [], + // }; + // } + // step.solution.commits = params.commits[stepSolutionKey]; + // } + + // // add markdown + // const stepMarkdown: Partial = + // mdContent.levels[level.id].steps[step.id]; + // if (stepMarkdown) { + // step = { ...step, ...stepMarkdown }; + // } + + // step.id = `${stepKey}`; + // return step; + // } + // ); + // } catch (error) { + // console.log(JSON.stringify(level.steps)); + // console.error("Error parsing level steps"); + // console.error(error.message); + // } + + console.log(params.commits); + + if (params.commits[level.id]) { + if (!level.setup) { + level.setup = {}; + } + level.setup.commits = params.commits[level.id]; } - } - // add level step commits - // try { - // level.steps = (level.steps || []).map( - // (step: T.Step, stepIndex: number) => { - // const stepKey = `${levelId}S${stepIndex + 1}`; - // const stepSetupKey = `${stepKey}Q`; - // if (params.commits[stepSetupKey]) { - // if (!step.setup) { - // step.setup = { - // commits: [], - // }; - // } - // step.setup.commits = params.commits[stepSetupKey]; - // } - - // const stepSolutionKey = `${stepKey}A`; - // if (params.commits[stepSolutionKey]) { - // if (!step.solution) { - // step.solution = { - // commits: [], - // }; - // } - // step.solution.commits = params.commits[stepSolutionKey]; - // } - - // // add markdown - // const stepMarkdown: Partial = - // mdContent.levels[level.id].steps[step.id]; - // if (stepMarkdown) { - // step = { ...step, ...stepMarkdown }; - // } - - // step.id = `${stepKey}`; - // return step; - // } - // ); - // } catch (error) { - // console.log(JSON.stringify(level.steps)); - // console.error("Error parsing level steps"); - // console.error(error.message); - // } - - return level; - }); + return level; + } + ); return parsed; } diff --git a/tests/parse.test.ts b/tests/parse.test.ts index c83b6dc..77369c4 100644 --- a/tests/parse.test.ts +++ b/tests/parse.test.ts @@ -40,7 +40,7 @@ Some text `; const skeleton = { - levels: [{ id: "L1" }], + levels: [{ id: "1" }], }; const result = parse({ @@ -299,7 +299,7 @@ The first step text: md, skeleton, commits: { - L1S1Q: ["abcdefg1"], + "1.1Q": ["abcdefg1"], }, }); const expected = { @@ -355,7 +355,7 @@ The first step text: md, skeleton, commits: { - L1S1Q: ["abcdefg1", "123456789"], + "1.1Q": ["abcdefg1", "123456789"], }, }); const expected = { @@ -406,7 +406,7 @@ The first step text: md, skeleton, commits: { - L1: ["abcdefg1"], + "1": ["abcdefg1"], }, }); const expected = { @@ -464,8 +464,8 @@ Another line text: md, skeleton, commits: { - L1: ["abcdefg1"], - L1S1Q: ["12345678"], + "1": ["abcdefg1"], + "1.1Q": ["12345678"], }, }); const expected = { @@ -519,8 +519,8 @@ The first step text: md, skeleton, commits: { - L1S1Q: ["abcdefg1", "123456789"], - L1S1A: ["1gfedcba", "987654321"], + "1.1Q": ["abcdefg1", "123456789"], + "1.1A": ["1gfedcba", "987654321"], }, }); const expected = { @@ -644,12 +644,12 @@ The third step text: md, skeleton, commits: { - L1S1Q: ["abcdef1", "123456789"], - L1S1A: ["1fedcba", "987654321"], - L1S2Q: ["2abcdef"], - L1S2A: ["3abcdef"], - L2S1Q: ["4abcdef"], - L2S1A: ["5abcdef"], + "1.1Q": ["abcdef1", "123456789"], + "1.1A": ["1fedcba", "987654321"], + "1.2Q": ["2abcdef"], + "1.2A": ["3abcdef"], + "2.1Q": ["4abcdef"], + "2.1A": ["5abcdef"], }, }); const expected = { @@ -664,7 +664,7 @@ The third step content: "First level content.", steps: [ { - id: "L11.1S1", + id: "1.1", content: "The first step", setup: { commits: ["abcdef1", "123456789"], @@ -759,7 +759,7 @@ The first step text: md, skeleton, commits: { - L1S1Q: ["abcdef1", "123456789"], + "1.1Q": ["abcdef1", "123456789"], }, }); const expected = { @@ -935,7 +935,7 @@ Description. }); }); - xdescribe("hints", () => { + describe("hints", () => { it("should parse hints for a step", () => { const md = `# Title @@ -971,7 +971,7 @@ The first step text: md, skeleton, commits: { - L1S1Q: ["abcdef1", "123456789"], + "1.1Q": ["abcdef1", "123456789"], }, }); const expected = { @@ -1040,7 +1040,7 @@ And spans multiple lines. text: md, skeleton, commits: { - L1S1Q: ["abcdef1", "123456789"], + "1.1Q": ["abcdef1", "123456789"], }, }); const expected = { @@ -1119,9 +1119,9 @@ The second uninterrupted step text: md, skeleton, commits: { - L1S1Q: ["abcdef1"], - L1S1A: ["123456789"], - L1S2Q: ["fedcba1"], + "1.1Q": ["abcdef1"], + "1.1A": ["123456789"], + "1.2Q": ["fedcba1"], }, }); const expected = { diff --git a/tests/skeleton.test.ts b/tests/skeleton.test.ts index 29cbe9a..57a5f3c 100644 --- a/tests/skeleton.test.ts +++ b/tests/skeleton.test.ts @@ -186,7 +186,7 @@ describe("validate skeleton", () => { const valid = validateSkeleton(json); expect(valid).toBe(false); }); - it("should fial if level is missing id", () => { + it("should fail if level is missing id", () => { const level1 = { ...validJson.levels[0], id: undefined }; const json = { ...validJson, diff --git a/typings/tutorial.d.ts b/typings/tutorial.d.ts index 5484d57..2b7769c 100644 --- a/typings/tutorial.d.ts +++ b/typings/tutorial.d.ts @@ -25,8 +25,8 @@ export type Level = { export type Step = { id: string; content: string; - setup: StepActions; - solution: Maybe; + setup?: StepActions; + solution?: Maybe; subtasks?: { [testName: string]: boolean }; hints?: string[]; }; From b0d2369d7f09db9791f4f8761a5770b823dc5c37 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 21:01:01 -0700 Subject: [PATCH 06/17] complete parser tests Signed-off-by: shmck --- src/utils/parse.ts | 104 ++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 72 deletions(-) diff --git a/src/utils/parse.ts b/src/utils/parse.ts index f5cfd46..54114e2 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -45,7 +45,7 @@ export function parseMdContent(md: string): TutorialFrame | never { mdContent.summary.description = summaryMatch.groups.tutorialDescription.trim(); } - let current = { level: -1, step: 0 }; + let current = { level: -1, step: -1 }; // Identify each part of the content parts.forEach((section: string) => { // match level @@ -53,7 +53,7 @@ export function parseMdContent(md: string): TutorialFrame | never { const levelMatch: RegExpMatchArray | null = section.match(levelRegex); if (levelMatch && levelMatch.groups) { - current = { level: current.level + 1, step: 0 }; + current = { level: current.level + 1, step: -1 }; const { levelTitle, levelSummary, levelContent } = levelMatch.groups; // @ts-ignore @@ -75,15 +75,15 @@ export function parseMdContent(md: string): TutorialFrame | never { const stepRegex = /^(#{3}\s(?.*)[\n\r]+(?[^]*))/; const stepMatch: RegExpMatchArray | null = section.match(stepRegex); if (stepMatch && stepMatch.groups) { + current = { level: current.level, step: current.step + 1 }; const { stepId, stepContent } = stepMatch.groups; mdContent.levels[current.level].steps[current.step] = { id: `${current.level + 1}.${current.step + 1}`, content: stepContent.trim(), }; - current = { ...current, step: current.step + 1 }; } else { // parse hints from stepContent - const hintDetectRegex = /^(#{4}\sHINTS[\n\r]+(\*\s(?[^]*))[\n\r]+)+/; + const hintDetectRegex = /^(#{4}\sHINTS[\n\r]+([\*|\-]\s(?[^]*))[\n\r]+)+/; const hintMatch = section.match(hintDetectRegex); if (!!hintMatch) { const hintItemRegex = /[\n\r]+\*\s/; @@ -142,34 +142,37 @@ export function parse(params: ParseParams): any { level = { ...configLevelProps, ...level }; if (steps) { steps.forEach((step: T.Step, index: number) => { - console.log("step", step); - const mdStep = level.steps[index]; - console.log("mdStep", mdStep); - step = { - ...step, - ...mdStep, - }; - - const stepKey = step.id; - console.log("stepKey", stepKey); - const stepSetupKey = `${stepKey}Q`; - if (params.commits[stepSetupKey]) { - if (!step.setup) { - step.setup = { - commits: [], - }; + try { + const mdStep = level.steps[index]; + + step = { + ...step, + ...mdStep, + }; + + const stepSetupKey = `${step.id}Q`; + if (params.commits[stepSetupKey]) { + if (!step.setup) { + step.setup = { + commits: [], + }; + } + step.setup.commits = params.commits[stepSetupKey]; } - step.setup.commits = params.commits[stepSetupKey]; - } - const stepSolutionKey = `${stepKey}A`; - if (params.commits[stepSolutionKey]) { - if (!step.solution) { - step.solution = { - commits: [], - }; + const stepSolutionKey = `${step.id}A`; + if (params.commits[stepSolutionKey]) { + if (!step.solution) { + step.solution = { + commits: [], + }; + } + step.solution.commits = params.commits[stepSolutionKey]; } - step.solution.commits = params.commits[stepSolutionKey]; + } catch (error) { + console.error("Error parsing level steps"); + console.warn(JSON.stringify(level.steps)); + console.error(error.message); } // update level step level.steps[index] = step; @@ -177,49 +180,6 @@ export function parse(params: ParseParams): any { } } - // try { - // level.steps = (level.steps || []).map( - // (step: T.Step, stepIndex: number) => { - // const stepKey = `${levelId}S${stepIndex + 1}`; - // const stepSetupKey = `${stepKey}Q`; - // if (params.commits[stepSetupKey]) { - // if (!step.setup) { - // step.setup = { - // commits: [], - // }; - // } - // step.setup.commits = params.commits[stepSetupKey]; - // } - - // const stepSolutionKey = `${stepKey}A`; - // if (params.commits[stepSolutionKey]) { - // if (!step.solution) { - // step.solution = { - // commits: [], - // }; - // } - // step.solution.commits = params.commits[stepSolutionKey]; - // } - - // // add markdown - // const stepMarkdown: Partial = - // mdContent.levels[level.id].steps[step.id]; - // if (stepMarkdown) { - // step = { ...step, ...stepMarkdown }; - // } - - // step.id = `${stepKey}`; - // return step; - // } - // ); - // } catch (error) { - // console.log(JSON.stringify(level.steps)); - // console.error("Error parsing level steps"); - // console.error(error.message); - // } - - console.log(params.commits); - if (params.commits[level.id]) { if (!level.setup) { level.setup = {}; From 5dd69ce04406e730a8f9bc409edfb58521a31d56 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 21:15:23 -0700 Subject: [PATCH 07/17] remove commit validation in skeleton Signed-off-by: shmck --- src/schema/meta.ts | 35 +++++++++++++++++++++++++++++++++++ src/schema/skeleton.ts | 16 ++++++++-------- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/schema/meta.ts b/src/schema/meta.ts index 71bf7b8..97479f4 100644 --- a/src/schema/meta.ts +++ b/src/schema/meta.ts @@ -95,5 +95,40 @@ export default { }, additionalProperties: false, }, + setup_action_without_commits: { + type: "object", + description: + "A collection of files/commands that run when a level/step or solution is loaded", + properties: { + files: { + $ref: "#/definitions/file_array", + }, + commands: { + $ref: "#/definitions/command_array", + }, + watchers: { + type: "array", + items: { + $ref: "#/definitions/file_path", + // uniqueItems: true, + }, + description: + "An array file paths that, when updated, will trigger the test runner to run", + }, + filter: { + type: "string", + description: + "A regex pattern that will be passed to the test runner to limit the number of tests running", + examples: ["^TestSuiteName"], + }, + subtasks: { + type: "boolean", + description: + 'A feature that shows subtasks: all active test names and the status of the tests (pass/fail). Use together with "filter"', + examples: [true], + }, + }, + additionalProperties: false, + }, }, }; diff --git a/src/schema/skeleton.ts b/src/schema/skeleton.ts index 4f57b63..35c9353 100644 --- a/src/schema/skeleton.ts +++ b/src/schema/skeleton.ts @@ -53,9 +53,9 @@ export default { examples: ["coderoad"], }, setup: { - $ref: "#/definitions/setup_action", + $ref: "#/definitions/setup_action_without_commits", description: - "Setup commits or commands used for setting up the test runner on tutorial launch", + "Setup actions or commands used for setting up the test runner on tutorial launch", }, }, required: ["command", "args"], @@ -135,9 +135,9 @@ export default { examples: ["L1", "L11"], }, setup: { - $ref: "#/definitions/setup_action", + $ref: "#/definitions/setup_action_without_commits", description: - "An optional point for loading commits, running commands or opening files", + "An optional point for running actions, commands or opening files", }, steps: { type: "array", @@ -152,18 +152,18 @@ export default { setup: { allOf: [ { - $ref: "#/definitions/setup_action", + $ref: "#/definitions/setup_action_without_commits", description: - "A point for loading commits. It can also run commands and/or open files", + "A point for running actions, commands and/or opening files", }, ], }, solution: { allOf: [ { - $ref: "#/definitions/setup_action", + $ref: "#/definitions/setup_action_without_commits", description: - "The solution commits that can be loaded if the user gets stuck. It can also run commands and/or open files", + "The solution can be loaded if the user gets stuck. It can run actions, commands and/or open files", }, { required: [], From 791212e416c810f944bfa3cec290b32accbe1322 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 21:23:36 -0700 Subject: [PATCH 08/17] make step solution optional Signed-off-by: shmck --- src/build.ts | 2 +- src/schema/tutorial.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build.ts b/src/build.ts index 150dbc0..cd7dbc2 100644 --- a/src/build.ts +++ b/src/build.ts @@ -102,7 +102,7 @@ async function build(args: string[]) { try { const valid = validateSchema(skeletonSchema, skeleton); if (!valid) { - console.error("Tutorial validation failed. See above to see what to fix"); + console.error("Skeleton validation failed. See above to see what to fix"); return; } } catch (e) { diff --git a/src/schema/tutorial.ts b/src/schema/tutorial.ts index 6a605c9..062c9cc 100644 --- a/src/schema/tutorial.ts +++ b/src/schema/tutorial.ts @@ -211,7 +211,7 @@ export default { examples: ["Have you tried doing X?"], }, }, - required: ["content", "setup", "solution"], + required: ["content", "setup"], }, }, }, From 67b01953451d76c8ac68d19b21cde135b80a57f1 Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 21:38:01 -0700 Subject: [PATCH 09/17] fix multiple commit order issue Signed-off-by: shmck --- src/utils/commits.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/commits.ts b/src/utils/commits.ts index 48fdee2..5d06662 100644 --- a/src/utils/commits.ts +++ b/src/utils/commits.ts @@ -73,7 +73,7 @@ export async function getCommits({ commits[position] = [commit.hash]; } else { // add to the list - commits[position].push(commit.hash); + commits[position].unshift(commit.hash); } positions.unshift(position); } else { @@ -84,7 +84,7 @@ export async function getCommits({ commits.INIT = [commit.hash]; } else { // add to the list - commits.INIT.push(commit.hash); + commits.INIT.unshift(commit.hash); } positions.unshift("INIT"); } @@ -101,5 +101,7 @@ export async function getCommits({ await rmdir(tmpDir, { recursive: true }); } + console.log(commits); + return commits; } From 8558b8e8d0158b41120f68a236d1cbbfd2361a5d Mon Sep 17 00:00:00 2001 From: shmck Date: Sat, 20 Jun 2020 21:39:55 -0700 Subject: [PATCH 10/17] fix step commit parsing Signed-off-by: shmck --- src/utils/commits.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/commits.ts b/src/utils/commits.ts index 5d06662..266fb57 100644 --- a/src/utils/commits.ts +++ b/src/utils/commits.ts @@ -62,7 +62,7 @@ export async function getCommits({ for (const commit of logs.all) { const matches = commit.message.match( - /^(?(?L\d+)(S\d+))(?[QA])?/ + /^(?(?L?\d+)([S|\.]\d+))(?[QA])?/ ); if (matches && matches.length) { From 692cb660b744920aed3c470c96125d5e9e45cb58 Mon Sep 17 00:00:00 2001 From: shmck Date: Mon, 22 Jun 2020 20:03:53 -0700 Subject: [PATCH 11/17] setup commit parsing for testing Signed-off-by: shmck --- src/utils/commits.ts | 80 +++++++++++++++++++----------------- src/utils/parse.ts | 8 ++++ src/utils/validateCommits.ts | 6 ++- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/utils/commits.ts b/src/utils/commits.ts index 266fb57..bd44b53 100644 --- a/src/utils/commits.ts +++ b/src/utils/commits.ts @@ -1,6 +1,7 @@ import * as fs from "fs"; import util from "util"; import * as path from "path"; +import { ListLogSummary } from "simple-git/typings/response"; import gitP, { SimpleGit } from "simple-git/promise"; import { validateCommitOrder } from "./validateCommits"; @@ -15,6 +16,44 @@ type GetCommitOptions = { export type CommitLogObject = { [position: string]: string[] }; +export function parseCommits(logs: ListLogSummary) { + // Filter relevant logs + const commits: CommitLogObject = {}; + const positions: string[] = []; + + for (const commit of logs.all) { + const matches = commit.message.match( + /^(?(?L?\d+)([S|\.]\d+))(?[Q|A|T|S])?/ + ); + + if (matches && matches.length) { + // Use an object of commit arrays to collect all commits + const position = matches[0]; + if (!commits[position]) { + // does not exist, create the list + commits[position] = [commit.hash]; + } else { + // add to the list + commits[position].unshift(commit.hash); + } + positions.unshift(position); + } else { + const initMatches = commit.message.match(/^INIT/); + if (initMatches && initMatches.length) { + if (!commits.INIT) { + // does not exist, create the list + commits.INIT = [commit.hash]; + } else { + // add to the list + commits.INIT.unshift(commit.hash); + } + positions.unshift("INIT"); + } + } + } + return { commits, positions }; +} + export async function getCommits({ localDir, codeBranch, @@ -49,48 +88,19 @@ export async function getCommits({ // track the original branch in case of failure const originalBranch = branches.current; - // Filter relevant logs - const commits: CommitLogObject = {}; - try { // Checkout the code branches await git.checkout(codeBranch); // Load all logs const logs = await git.log(); - const positions: string[] = []; - for (const commit of logs.all) { - const matches = commit.message.match( - /^(?(?L?\d+)([S|\.]\d+))(?[QA])?/ - ); + const { commits, positions } = parseCommits(logs); - if (matches && matches.length) { - // Use an object of commit arrays to collect all commits - const position = matches[0]; - if (!commits[position]) { - // does not exist, create the list - commits[position] = [commit.hash]; - } else { - // add to the list - commits[position].unshift(commit.hash); - } - positions.unshift(position); - } else { - const initMatches = commit.message.match(/^INIT/); - if (initMatches && initMatches.length) { - if (!commits.INIT) { - // does not exist, create the list - commits.INIT = [commit.hash]; - } else { - // add to the list - commits.INIT.unshift(commit.hash); - } - positions.unshift("INIT"); - } - } - } + // validate order validateCommitOrder(positions); + + return commits; } catch (e) { console.error("Error with checkout or commit matching"); throw new Error(e.message); @@ -100,8 +110,4 @@ export async function getCommits({ // cleanup the tmp directory await rmdir(tmpDir, { recursive: true }); } - - console.log(commits); - - return commits; } diff --git a/src/utils/parse.ts b/src/utils/parse.ts index 54114e2..805ea0f 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -187,6 +187,14 @@ export function parse(params: ParseParams): any { level.setup.commits = params.commits[level.id]; } + // @deprecated L1 system + if (params.commits[`L${level.id}`]) { + if (!level.setup) { + level.setup = {}; + } + level.setup.commits = params.commits[`L${level.id}`]; + } + return level; } ); diff --git a/src/utils/validateCommits.ts b/src/utils/validateCommits.ts index 68cfaa4..9e990fb 100644 --- a/src/utils/validateCommits.ts +++ b/src/utils/validateCommits.ts @@ -13,8 +13,10 @@ export function validateCommitOrder(positions: string[]): boolean { current = { level: 0, step: 0 }; return; } else { - const levelMatch = position.match(/^L?([0-9]+)Q?$/); - const stepMatch = position.match(/^L?([0-9]+)[S|\.]([0-9]+)[Q|A]?$/); + // @deprecate - remove L|Q + const levelMatch = position.match(/^L?([0-9]+)[Q|T]?$/); + // @deprecate - remove S|Q|A + const stepMatch = position.match(/^L?([0-9]+)[S|\.]([0-9]+)[Q|A|T|S]?$/); if (levelMatch) { // allows next level or step const [_, levelString] = levelMatch; From deba9b3c887b2e49724a8354850d1f31936d7d66 Mon Sep 17 00:00:00 2001 From: shmck Date: Mon, 22 Jun 2020 20:58:36 -0700 Subject: [PATCH 12/17] parse commits with tests Signed-off-by: shmck --- src/utils/commits.ts | 47 +++++++----- tests/commitParse.test.ts | 151 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 tests/commitParse.test.ts diff --git a/src/utils/commits.ts b/src/utils/commits.ts index bd44b53..17c2956 100644 --- a/src/utils/commits.ts +++ b/src/utils/commits.ts @@ -16,42 +16,52 @@ type GetCommitOptions = { export type CommitLogObject = { [position: string]: string[] }; -export function parseCommits(logs: ListLogSummary) { + + +export function parseCommits(logs: ListLogSummary): { [hash: string]: string[]} { // Filter relevant logs const commits: CommitLogObject = {}; const positions: string[] = []; for (const commit of logs.all) { const matches = commit.message.match( - /^(?(?L?\d+)([S|\.]\d+))(?[Q|A|T|S])?/ + /^(?INIT)|(L?(?\d+)[S|\.]?(?\d+)?(?[Q|A|T|S])?)/ ); if (matches && matches.length) { // Use an object of commit arrays to collect all commits - const position = matches[0]; - if (!commits[position]) { - // does not exist, create the list - commits[position] = [commit.hash]; + const { groups } = matches + let position + if (groups.init) { + position = 'INIT' + } else if (groups.levelId && groups.stepId) { + let stepType + // @deprecated Q + if (!groups.stepType || ['Q', 'T'].includes(groups.stepType)) { + stepType = 'T' // test + // @deprecated A + } else if (!groups.stepType || ['A', 'S'].includes(groups.stepType)) { + stepType = 'S' // solution + } + position = `${groups.levelId}.${groups.stepId}:${stepType}` + } else if (groups.levelId) { + position = groups.levelId } else { - // add to the list - commits[position].unshift(commit.hash); + console.warn(`No matcher for commit "${commit.message}"`) } + commits[position] = [...(commits[position] || []), commit.hash] positions.unshift(position); } else { const initMatches = commit.message.match(/^INIT/); if (initMatches && initMatches.length) { - if (!commits.INIT) { - // does not exist, create the list - commits.INIT = [commit.hash]; - } else { - // add to the list - commits.INIT.unshift(commit.hash); - } + commits.INIT = [...(commits.INIT || []), commit.hash] positions.unshift("INIT"); } } } - return { commits, positions }; + // validate order + validateCommitOrder(positions); + return commits; } export async function getCommits({ @@ -95,10 +105,7 @@ export async function getCommits({ // Load all logs const logs = await git.log(); - const { commits, positions } = parseCommits(logs); - - // validate order - validateCommitOrder(positions); + const commits = parseCommits(logs); return commits; } catch (e) { diff --git a/tests/commitParse.test.ts b/tests/commitParse.test.ts new file mode 100644 index 0000000..00e1548 --- /dev/null +++ b/tests/commitParse.test.ts @@ -0,0 +1,151 @@ +import { parseCommits } from "../src/utils/commits"; + +describe("commitParse", () => { + it("should parse out #. commits", () => { + const logs = { + all: [ + { + message: "INIT", + hash: "1", + }, + { + message: "1. First Level", + hash: "2", + }, + { + message: "1.1 First Step", + hash: "3", + }, + ], + total: 2, + latest: {}, + }; + const commits = parseCommits(logs); + expect(commits).toEqual({ + INIT: ["1"], + "1": ["2"], + "1.1:T": ["3"], + }); + }); + // @deprecated - remove L# + it("should parse out L# commits", () => { + const logs = { + all: [ + { + message: "INIT", + hash: "1", + }, + { + message: "L1 First Level", + hash: "2", + }, + { + message: "L1S1 First Step", + hash: "3", + }, + ], + total: 2, + latest: {}, + }; + const commits = parseCommits(logs); + expect(commits).toEqual({ + INIT: ["1"], + "1": ["2"], + "1.1:T": ["3"], + }); + }); + // @deprecated - remove with QA + it("should parse out #.Q|A commits", () => { + const logs = { + all: [ + { + message: "INIT", + hash: "1", + }, + { + message: "1. First Level", + hash: "2", + }, + { + message: "1.1Q First Step", + hash: "3", + }, + { + message: "1.1A First Step Solution", + hash: "4", + }, + ], + total: 2, + latest: {}, + }; + const commits = parseCommits(logs); + expect(commits).toEqual({ + INIT: ["1"], + "1": ["2"], + "1.1:T": ["3"], + "1.1:S": ["4"], + }); + }); + it("should parse out #.T|S commits", () => { + const logs = { + all: [ + { + message: "INIT", + hash: "1", + }, + { + message: "1. First Level", + hash: "2", + }, + { + message: "1.1T First Step", + hash: "3", + }, + { + message: "1.1S First Step Solution", + hash: "4", + }, + ], + total: 2, + latest: {}, + }; + const commits = parseCommits(logs); + expect(commits).toEqual({ + INIT: ["1"], + "1": ["2"], + "1.1:T": ["3"], + "1.1:S": ["4"], + }); + }); + it("should parse out #._|S commits", () => { + const logs = { + all: [ + { + message: "INIT", + hash: "1", + }, + { + message: "1. First Level", + hash: "2", + }, + { + message: "1.1 First Step", + hash: "3", + }, + { + message: "1.1S First Step Solution", + hash: "4", + }, + ], + total: 2, + latest: {}, + }; + const commits = parseCommits(logs); + expect(commits).toEqual({ + INIT: ["1"], + "1": ["2"], + "1.1:T": ["3"], + "1.1:S": ["4"], + }); + }); +}); From f6159c443f254caf2b9aa28191240d6ec981c8ef Mon Sep 17 00:00:00 2001 From: shmck Date: Tue, 23 Jun 2020 19:48:00 -0700 Subject: [PATCH 13/17] validate commits Signed-off-by: shmck --- src/utils/validateCommits.ts | 19 +++++++--- tests/commitOrder.test.ts | 68 ++++++------------------------------ 2 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/utils/validateCommits.ts b/src/utils/validateCommits.ts index 9e990fb..b7373f0 100644 --- a/src/utils/validateCommits.ts +++ b/src/utils/validateCommits.ts @@ -14,17 +14,28 @@ export function validateCommitOrder(positions: string[]): boolean { return; } else { // @deprecate - remove L|Q - const levelMatch = position.match(/^L?([0-9]+)[Q|T]?$/); + const levelMatch = position.match(/^(?[0-9]+)$/); // @deprecate - remove S|Q|A - const stepMatch = position.match(/^L?([0-9]+)[S|\.]([0-9]+)[Q|A|T|S]?$/); + const stepMatch = position.match( + /^(?[0-9]+)\.(?[0-9]+):[T|S]$/ + ); if (levelMatch) { // allows next level or step - const [_, levelString] = levelMatch; + const levelString = levelMatch?.groups?.level; + if (!levelString) { + console.warn(`No commit level match for ${position}`); + return; + } const level = Number(levelString); current = { level, step: 0 }; } else if (stepMatch) { // allows next level or step - const [_, levelString, stepString] = stepMatch; + if (!stepMatch?.groups?.level || !stepMatch?.groups.step) { + console.warn(`No commit step match for ${position}`); + return; + } + const { level: levelString, step: stepString } = stepMatch.groups; + const level = Number(levelString); const step = Number(stepString); current = { level, step }; diff --git a/tests/commitOrder.test.ts b/tests/commitOrder.test.ts index 478b433..60aecbb 100644 --- a/tests/commitOrder.test.ts +++ b/tests/commitOrder.test.ts @@ -3,7 +3,7 @@ import { validateCommitOrder } from "../src/utils/validateCommits"; describe("commitOrder", () => { describe("#.# format", () => { it("should return true if order is valid", () => { - const positions = ["INIT", "1", "1.1", "1.2", "2", "2.1"]; + const positions = ["INIT", "1", "1.1:T", "1.2:T", "2", "2.1:T"]; const result = validateCommitOrder(positions); expect(result).toBe(true); }); @@ -13,81 +13,35 @@ describe("commitOrder", () => { "INIT", "1", "1", - "1.1", - "1.1", - "1.2", - "1.2", + "1.1:T", + "1.1:S", + "1.2:T", + "1.2:S", "2", "2", - "2.1", - "2.1", + "2.1:T", + "2.1:S", ]; const result = validateCommitOrder(positions); expect(result).toBe(true); }); it("should return false if INIT is out of order", () => { - const positions = ["INIT", "1", "1.1", "1.2", "INIT", "2", "2.1"]; + const positions = ["INIT", "1", "1.1:T", "1.2:T", "INIT", "2", "2.1:T"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); it("should return false if level after step is out of order", () => { - const positions = ["INIT", "1", "1.1", "1.2", "2.1", "2"]; + const positions = ["INIT", "1", "1.1:T", "1.2:T", "2.1:T", "2"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); it("should return false if level is out of order", () => { - const positions = ["INIT", "1", "L3", "2"]; + const positions = ["INIT", "1", "3", "2"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); it("should return false if step is out of order", () => { - const positions = ["INIT", "1", "1.1", "1.3", "1.2"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - }); - // @deprecated - describe("L#S# format", () => { - it("should return true if order is valid", () => { - const positions = ["INIT", "L1", "L1S1", "L1S2", "L2", "L2S1"]; - const result = validateCommitOrder(positions); - expect(result).toBe(true); - }); - it("should return true if valid with duplicates", () => { - const positions = [ - "INIT", - "INIT", - "L1", - "L1", - "L1S1", - "L1S1", - "L1S2", - "L1S2", - "L2", - "L2", - "L2S1", - "L2S1", - ]; - const result = validateCommitOrder(positions); - expect(result).toBe(true); - }); - it("should return false if INIT is out of order", () => { - const positions = ["INIT", "L1", "L1S1", "L1S2", "INIT", "L2", "L2S1"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - it("should return false if level after step is out of order", () => { - const positions = ["INIT", "L1", "L1S1", "L1S2", "L2S1", "L2"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - it("should return false if level is out of order", () => { - const positions = ["INIT", "L1", "L3", "L2"]; - const result = validateCommitOrder(positions); - expect(result).toBe(false); - }); - it("should return false if step is out of order", () => { - const positions = ["INIT", "L1", "L1S1", "L1S3", "L1S2"]; + const positions = ["INIT", "1", "1.1:T", "1.3:T", "1.2:T"]; const result = validateCommitOrder(positions); expect(result).toBe(false); }); From 0765174b98476ea5ab672b6696246f895e0ec9e8 Mon Sep 17 00:00:00 2001 From: shmck Date: Tue, 23 Jun 2020 20:06:20 -0700 Subject: [PATCH 14/17] validate step has tests && tests before solution Signed-off-by: shmck --- src/utils/validateCommits.ts | 25 +++++++++++++++++++------ tests/commitOrder.test.ts | 12 ++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/utils/validateCommits.ts b/src/utils/validateCommits.ts index b7373f0..f4d8890 100644 --- a/src/utils/validateCommits.ts +++ b/src/utils/validateCommits.ts @@ -3,21 +3,21 @@ export function validateCommitOrder(positions: string[]): boolean { // loop over positions const errors: number[] = []; - let previous = { level: 0, step: 0 }; - let current = { level: 0, step: 0 }; + let previous = { level: 0, step: 0, type: "" }; + let current = { level: 0, step: 0, type: "" }; positions.forEach((position: string, index: number) => { if (position === "INIT") { if (previous.level !== 0 && previous.step !== 0) { errors.push(index); } - current = { level: 0, step: 0 }; + current = { level: 0, step: 0, type: "" }; return; } else { // @deprecate - remove L|Q const levelMatch = position.match(/^(?[0-9]+)$/); // @deprecate - remove S|Q|A const stepMatch = position.match( - /^(?[0-9]+)\.(?[0-9]+):[T|S]$/ + /^(?[0-9]+)\.(?[0-9]+):(?[T|S])$/ ); if (levelMatch) { // allows next level or step @@ -27,7 +27,7 @@ export function validateCommitOrder(positions: string[]): boolean { return; } const level = Number(levelString); - current = { level, step: 0 }; + current = { level, step: 0, type: "" }; } else if (stepMatch) { // allows next level or step if (!stepMatch?.groups?.level || !stepMatch?.groups.step) { @@ -38,13 +38,26 @@ export function validateCommitOrder(positions: string[]): boolean { const level = Number(levelString); const step = Number(stepString); - current = { level, step }; + const type = stepMatch?.groups.stepType; + + const sameStep = previous.level === level && previous.step === step; + + if ( + // tests should come before the solution + (sameStep && type === "T" && previous.type === "S") || + // step should have tests + (!sameStep && type === "S") + ) { + errors.push(index); + } + current = { level, step, type }; } else { // error console.warn(`Invalid commit position: ${position}`); return; } if ( + // levels or steps are out of order current.level < previous.level || (current.level === previous.level && current.step < previous.step) ) { diff --git a/tests/commitOrder.test.ts b/tests/commitOrder.test.ts index 60aecbb..640b25f 100644 --- a/tests/commitOrder.test.ts +++ b/tests/commitOrder.test.ts @@ -14,6 +14,8 @@ describe("commitOrder", () => { "1", "1", "1.1:T", + "1.1:T", + "1.1:S", "1.1:S", "1.2:T", "1.2:S", @@ -45,5 +47,15 @@ describe("commitOrder", () => { const result = validateCommitOrder(positions); expect(result).toBe(false); }); + it("should return false if solution is before step", () => { + const positions = ["INIT", "1", "1.1:S", "1.1:T", "1.2:T"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); + it("should return false if solution but no test step", () => { + const positions = ["INIT", "1", "1.1:S", "1.2:T"]; + const result = validateCommitOrder(positions); + expect(result).toBe(false); + }); }); }); From 1b7d20b77f33e10d513f2e9d1852acdb1e440cfc Mon Sep 17 00:00:00 2001 From: shmck Date: Tue, 23 Jun 2020 20:13:03 -0700 Subject: [PATCH 15/17] update step ids Signed-off-by: shmck --- src/utils/parse.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/parse.ts b/src/utils/parse.ts index 805ea0f..443fbf5 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -150,7 +150,7 @@ export function parse(params: ParseParams): any { ...mdStep, }; - const stepSetupKey = `${step.id}Q`; + const stepSetupKey = `${step.id}:T`; if (params.commits[stepSetupKey]) { if (!step.setup) { step.setup = { @@ -160,7 +160,7 @@ export function parse(params: ParseParams): any { step.setup.commits = params.commits[stepSetupKey]; } - const stepSolutionKey = `${step.id}A`; + const stepSolutionKey = `${step.id}:S`; if (params.commits[stepSolutionKey]) { if (!step.solution) { step.solution = { From 325ba8b1447c67cac31603360fe578c965356197 Mon Sep 17 00:00:00 2001 From: shmck Date: Tue, 23 Jun 2020 20:16:44 -0700 Subject: [PATCH 16/17] update docs for new build format Signed-off-by: shmck --- README.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 482e61c..14502cb 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,11 @@ The configuration file is created by matching the `level` and `step` ids between Tutorial description. -## L1 This is a level with id = 1 +## 1. This is a level with id = 1 This level has two steps... -### L1S1 First step +### 1.1 First step The first step with id L1S1. The Step id should start with the level id. @@ -62,7 +62,7 @@ The first step with id L1S1. The Step id should start with the level id. - The second hint that will show - The third and final hint, as it is last in order -### L1S2 The second step +### 1.2 The second step The second step... ``` @@ -72,10 +72,10 @@ The second step... ```yaml --- levels: - - id: L1 + - id: "1" config: {} steps: - - id: L1S1 + - id: "1.1" setup: files: - package.json @@ -86,7 +86,7 @@ levels: - package.json commands: - npm install - - id: L1S2 + - id: "1.2" setup: files: - src/server.js @@ -104,23 +104,19 @@ commit 8e0e3a42ae565050181fdb68298114df21467a74 (HEAD -> v2, origin/v2) Author: creator Date: Sun May 3 16:16:01 2020 -0700 - L1S1Q setup step 1 for level 1 + 1.1 setup for level 1, step 1 commit 9499611fc9b311040dcabaf2d98439fc0c356cc9 Author: creator Date: Sun May 3 16:13:37 2020 -0700 - L1S2A checkout solution for level 1, step 2 + 1.1S solution for level 1, step 1 commit c5c62041282579b495d3589b2eb1fdda2bcd7155 Author: creator Date: Sun May 3 16:11:42 2020 -0700 - L1S2Q setup level 1, step 2 + 1.2 setup for level 1, step 2 ``` -Note that the step `L1S2` has two commits, one with the suffix `Q` and another one with `A`. The suffixes mean `Question` and `Answer`, respectively, and refer to the unit tests and the commit that makes them pass. - -Steps defined as questions are **required** as they are meant to set the task to be executed by the student. The answer is optional and should be used when a commit must be loaded to verify the student's solution. - -If there are multiple commits for a level or step, they are captured in order. +Note that the step `1.1` has two commits, one with the suffix `S`. The first commit refers to the required tests and setup, while the second optional commit contains the solution. If there are multiple commits for a level or step, they are captured in order. From aa745f9057592311b1f5f80382d9d083f30af326 Mon Sep 17 00:00:00 2001 From: shmck Date: Tue, 23 Jun 2020 20:17:07 -0700 Subject: [PATCH 17/17] prepare release Signed-off-by: shmck --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c870ac..30e5a83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@coderoad/cli", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5bdd89d..85e3bc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coderoad/cli", - "version": "0.3.1", + "version": "0.4.0", "description": "A CLI to build the configuration file for Coderoad Tutorials", "keywords": [ "coderoad",