

























@@ -1,8 +1,8 @@
1-import { describe, expect, it } from 'vitest';
2-import { parseMd } from '../parse.js';
1+import { describe, expect, it } from "vitest";
2+import { parseMd } from "../parse.js";
334-describe('parseMd — frontmatter', () => {
5-it('parses simple frontmatter', () => {
4+describe("parseMd — frontmatter", () => {
5+it("parses simple frontmatter", () => {
66const raw = `---
77name: github
88description: gh CLI for issues, PRs, runs
@@ -13,20 +13,20 @@ Body text.
1313const { ast, diagnostics } = parseMd(raw);
1414expect(diagnostics).toEqual([]);
1515expect(ast.frontmatter).toEqual([
16-{ key: 'name', value: 'github', line: 2 },
17-{ key: 'description', value: 'gh CLI for issues, PRs, runs', line: 3 },
16+{ key: "name", value: "github", line: 2 },
17+{ key: "description", value: "gh CLI for issues, PRs, runs", line: 3 },
1818]);
1919});
202021-it('handles no frontmatter', () => {
21+it("handles no frontmatter", () => {
2222const raw = `## First section\n\nContent.\n`;
2323const { ast } = parseMd(raw);
2424expect(ast.frontmatter).toEqual([]);
25-expect(ast.preamble).toBe('');
25+expect(ast.preamble).toBe("");
2626expect(ast.blocks.length).toBe(1);
2727});
282829-it('emits diagnostic for unclosed frontmatter', () => {
29+it("emits diagnostic for unclosed frontmatter", () => {
3030const raw = `---
3131name: github
3232description: never closes
@@ -35,24 +35,24 @@ Body.
3535`;
3636const { diagnostics } = parseMd(raw);
3737expect(diagnostics).toContainEqual(
38-expect.objectContaining({ code: 'OC_FRONTMATTER_UNCLOSED' }),
38+expect.objectContaining({ code: "OC_FRONTMATTER_UNCLOSED" }),
3939);
4040});
414142-it('strips quotes from values', () => {
42+it("strips quotes from values", () => {
4343const raw = `---
4444title: "Hello world"
4545hint: 'quoted'
4646---
4747`;
4848const { ast } = parseMd(raw);
49-expect(ast.frontmatter[0]?.value).toBe('Hello world');
50-expect(ast.frontmatter[1]?.value).toBe('quoted');
49+expect(ast.frontmatter[0]?.value).toBe("Hello world");
50+expect(ast.frontmatter[1]?.value).toBe("quoted");
5151});
5252});
535354-describe('parseMd — H2 blocks', () => {
55-it('splits sections', () => {
54+describe("parseMd — H2 blocks", () => {
55+it("splits sections", () => {
5656const raw = `Preamble text.
57575858## First
@@ -64,14 +64,14 @@ Body of first.
6464Body of second.
6565`;
6666const { ast } = parseMd(raw);
67-expect(ast.preamble.trim()).toBe('Preamble text.');
67+expect(ast.preamble.trim()).toBe("Preamble text.");
6868expect(ast.blocks.length).toBe(2);
69-expect(ast.blocks[0]?.heading).toBe('First');
70-expect(ast.blocks[0]?.slug).toBe('first');
71-expect(ast.blocks[1]?.heading).toBe('Second');
69+expect(ast.blocks[0]?.heading).toBe("First");
70+expect(ast.blocks[0]?.slug).toBe("first");
71+expect(ast.blocks[1]?.heading).toBe("Second");
7272});
737374-it('preserves line numbers (1-based)', () => {
74+it("preserves line numbers (1-based)", () => {
7575const raw = `Line 1
7676## Heading at line 2
7777Line 3
@@ -80,7 +80,7 @@ Line 3
8080expect(ast.blocks[0]?.line).toBe(2);
8181});
828283-it('does NOT split on `## ` inside fenced code blocks', () => {
83+it("does NOT split on `## ` inside fenced code blocks", () => {
8484const raw = `## Real section
85858686\`\`\`md
@@ -91,36 +91,36 @@ content
9191## Another section
9292`;
9393const { ast } = parseMd(raw);
94-expect(ast.blocks.map((b) => b.heading)).toEqual(['Real section', 'Another section']);
94+expect(ast.blocks.map((b) => b.heading)).toEqual(["Real section", "Another section"]);
9595});
9696});
979798-describe('parseMd — items', () => {
99-it('extracts plain bullet items', () => {
98+describe("parseMd — items", () => {
99+it("extracts plain bullet items", () => {
100100const raw = `## Boundaries
101101102102- never write to /etc
103103- always confirm before deleting
104104`;
105105const { ast } = parseMd(raw);
106106expect(ast.blocks[0]?.items.length).toBe(2);
107-expect(ast.blocks[0]?.items[0]?.text).toBe('never write to /etc');
107+expect(ast.blocks[0]?.items[0]?.text).toBe("never write to /etc");
108108expect(ast.blocks[0]?.items[0]?.kv).toBeUndefined();
109109});
110110111-it('extracts kv items', () => {
111+it("extracts kv items", () => {
112112const raw = `## Tools
113113114114- gh: GitHub CLI
115115- curl: HTTP client
116116`;
117117const { ast } = parseMd(raw);
118-expect(ast.blocks[0]?.items[0]?.kv).toEqual({ key: 'gh', value: 'GitHub CLI' });
119-expect(ast.blocks[0]?.items[0]?.slug).toBe('gh');
120-expect(ast.blocks[0]?.items[1]?.kv).toEqual({ key: 'curl', value: 'HTTP client' });
118+expect(ast.blocks[0]?.items[0]?.kv).toEqual({ key: "gh", value: "GitHub CLI" });
119+expect(ast.blocks[0]?.items[0]?.slug).toBe("gh");
120+expect(ast.blocks[0]?.items[1]?.kv).toEqual({ key: "curl", value: "HTTP client" });
121121});
122122123-it('does NOT extract bullets inside fenced code', () => {
123+it("does NOT extract bullets inside fenced code", () => {
124124const raw = `## Section
125125126126\`\`\`
@@ -131,12 +131,12 @@ describe('parseMd — items', () => {
131131`;
132132const { ast } = parseMd(raw);
133133expect(ast.blocks[0]?.items.length).toBe(1);
134-expect(ast.blocks[0]?.items[0]?.text).toBe('real bullet');
134+expect(ast.blocks[0]?.items[0]?.text).toBe("real bullet");
135135});
136136});
137137138-describe('parseMd — tables', () => {
139-it('extracts a simple table', () => {
138+describe("parseMd — tables", () => {
139+it("extracts a simple table", () => {
140140const raw = `## Tool Guidance
141141142142| tool | guidance |
@@ -146,15 +146,17 @@ describe('parseMd — tables', () => {
146146`;
147147const { ast } = parseMd(raw);
148148const table = ast.blocks[0]?.tables[0];
149-expect(table).toBeDefined();
150-expect(table?.headers).toEqual(['tool', 'guidance']);
151-expect(table?.rows.length).toBe(2);
152-expect(table?.rows[0]).toEqual(['gh', 'use for GitHub']);
149+if (!table) {
150+throw new Error("expected parsed markdown table");
151+}
152+expect(table.headers).toEqual(["tool", "guidance"]);
153+expect(table.rows.length).toBe(2);
154+expect(table.rows[0]).toEqual(["gh", "use for GitHub"]);
153155});
154156});
155157156-describe('parseMd — code blocks', () => {
157-it('extracts a fenced code block', () => {
158+describe("parseMd — code blocks", () => {
159+it("extracts a fenced code block", () => {
158160const raw = `## Examples
159161160162\`\`\`ts
@@ -163,12 +165,12 @@ const x = 1;
163165`;
164166const { ast } = parseMd(raw);
165167expect(ast.blocks[0]?.codeBlocks[0]).toMatchObject({
166-lang: 'ts',
167-text: 'const x = 1;',
168+lang: "ts",
169+text: "const x = 1;",
168170});
169171});
170172171-it('handles unlanguaged fences', () => {
173+it("handles unlanguaged fences", () => {
172174const raw = `## Block
173175174176\`\`\`
@@ -180,24 +182,24 @@ plain text
180182});
181183});
182184183-describe('parseMd — byte-fidelity', () => {
184-it('preserves raw on the AST', () => {
185+describe("parseMd — byte-fidelity", () => {
186+it("preserves raw on the AST", () => {
185187const raw = `---\nname: x\n---\n\n## Sec\n\n- a\n- b\n`;
186188const { ast } = parseMd(raw);
187189expect(ast.raw).toBe(raw);
188190});
189191190-it('preserves BOM in raw but ignores it for parsing', () => {
191-const raw = '## Heading\n';
192+it("preserves BOM in raw but ignores it for parsing", () => {
193+const raw = "## Heading\n";
192194const { ast } = parseMd(raw);
193195expect(ast.raw).toBe(raw);
194-expect(ast.blocks[0]?.heading).toBe('Heading');
196+expect(ast.blocks[0]?.heading).toBe("Heading");
195197});
196198197-it('handles CRLF line endings', () => {
198-const raw = '## Heading\r\n\r\n- item\r\n';
199+it("handles CRLF line endings", () => {
200+const raw = "## Heading\r\n\r\n- item\r\n";
199201const { ast } = parseMd(raw);
200-expect(ast.blocks[0]?.heading).toBe('Heading');
201-expect(ast.blocks[0]?.items[0]?.text).toBe('item');
202+expect(ast.blocks[0]?.heading).toBe("Heading");
203+expect(ast.blocks[0]?.items[0]?.text).toBe("item");
202204});
203205});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。