Files

192 lines
7.1 KiB
TypeScript

// docs-list tests cover source docs metadata discovery for docs-aware tooling.
import { execFileSync, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { renderDocsHeadingMap } from "../../scripts/docs-list.js";
import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js";
const tempDirs: string[] = [];
const repoRoot = path.resolve(import.meta.dirname, "../..");
const docsListScriptPath = path.join(repoRoot, "scripts", "docs-list.js");
function makeTempRepoRoot(prefix: string): string {
return makeTempDir(tempDirs, prefix);
}
function runDocsList(cwd: string, args: string[] = []): string {
return execFileSync(process.execPath, [docsListScriptPath, ...args], {
cwd,
encoding: "utf8",
});
}
afterEach(() => {
cleanupTempDirs(tempDirs);
});
describe("docs-list", () => {
it("reports a concise error outside a source checkout", () => {
const tempRepoRoot = makeTempRepoRoot("openclaw-docs-list-missing-");
const result = spawnSync(process.execPath, [docsListScriptPath], {
cwd: tempRepoRoot,
encoding: "utf8",
});
expect(result.status).toBe(1);
expect(result.stderr).toBe("docs:list: missing docs directory. Run from repo root.\n");
});
it("reads metadata across supported front matter forms", () => {
const tempRepoRoot = makeTempRepoRoot("openclaw-docs-list-");
mkdirSync(path.join(tempRepoRoot, "docs"), { recursive: true });
const cases = [
[
"inline.md",
'---\nsummary: "Single-line read_when page"\nread_when: "Read this page when the hint is inline."\n---\n',
"Single-line read_when page",
],
["yaml-end.md", '---\nsummary: "YAML document end page"\n...\n', "YAML document end page"],
[
"annotated.md",
'---\r\nsummary: "Annotated closing delimiter"\r\n--- # end\r\n',
"Annotated closing delimiter",
],
[
"whitespace.md",
'---\nsummary: "Whitespace closing delimiter"\n--- \n',
"Whitespace closing delimiter",
],
[
"document-end-comment.md",
'---\r\nsummary: "Document end comment"\r\n... # end\r\n',
"Document end comment",
],
] as const;
for (const [fileName, content] of cases) {
writeFileSync(path.join(tempRepoRoot, "docs", fileName), content, "utf8");
}
const output = runDocsList(tempRepoRoot);
for (const [fileName, , summary] of cases) {
expect(output).toContain(`${fileName} - ${summary}`);
}
expect(output).toContain("Read when: Read this page when the hint is inline.");
expect(output).not.toContain("unterminated front matter");
});
it("renders the publish docs map on demand without creating a mirror", () => {
const tempRepoRoot = makeTempRepoRoot("openclaw-docs-headings-");
mkdirSync(path.join(tempRepoRoot, "docs", "nested"), { recursive: true });
writeFileSync(
path.join(tempRepoRoot, "docs", "page.md"),
`---
summary: "Page"
# This metadata comment must not become a heading
... # end
# Visible title
## \`API[*]\` <script>alert(1)</script>
### <scr<script>ipt>alert(1)</script>
#### \`![label](https://example.test/image)\`
\`\`\`md
### Hidden fenced heading
\`\`\`
\`\`\`\`md
\`\`\`json
### Hidden nested fenced heading
\`\`\`
\`\`\`\`
~~~md
### Hidden fenced heading with a suffixed closer
~~~json
### Still hidden after the invalid closer
~~~
# Visible after the fenced block
`,
"utf8",
);
writeFileSync(path.join(tempRepoRoot, "docs", "nested", "index.mdx"), "# Nested\n");
writeFileSync(path.join(tempRepoRoot, "docs", "AGENTS.md"), "# Instructions\n");
const output = runDocsList(tempRepoRoot, ["--headings"]);
expect(output).toContain("## page.md\n\n- Route: /page");
expect(output).toContain(" - H1: Visible title");
expect(output).toContain(" - H2: `API[*]` &lt;script&gt;alert(1)&lt;/script&gt;");
expect(output).toContain(" - H3: &lt;scr&lt;script&gt;ipt&gt;alert(1)&lt;/script&gt;");
expect(output).toContain(" - H4: `![label](https://example.test/image)`");
expect(output).toContain("## nested/index.mdx\n\n- Route: /nested");
expect(output).not.toContain("metadata comment must not become a heading");
expect(output).not.toContain("Hidden fenced heading");
expect(output).not.toContain("Hidden nested fenced heading");
expect(output).not.toContain("Still hidden after the invalid closer");
expect(output).toContain(" - H1: Visible after the fenced block");
expect(output).not.toContain("AGENTS.md");
expect(existsSync(path.join(tempRepoRoot, "docs", "docs_map.md"))).toBe(false);
});
it.each([
["backtick text suffix", "```md", "```json", "```"],
["backtick nonbreaking space", "```md", "```\u00a0", "```"],
["tilde nonbreaking space", "~~~md", "~~~\u00a0", "~~~"],
["line separator in opening info", "```md\u2028info", "```json", "```"],
["paragraph separator in opening info", "~~~md\u2029info", "~~~json", "~~~"],
["different fence character", "```md", "~~~", "```"],
])("keeps example headings hidden with %s", (_name, opening, invalidClosing, closing) => {
const tempRepoRoot = makeTempRepoRoot("openclaw-docs-fence-");
mkdirSync(path.join(tempRepoRoot, "docs"));
writeFileSync(
path.join(tempRepoRoot, "docs", "page.md"),
`${opening}\n# Hidden example\n${invalidClosing}\n## Still hidden\n${closing}\n# Visible after close\n`,
);
const output = renderDocsHeadingMap(path.join(tempRepoRoot, "docs"));
expect(output).not.toContain("H1: Hidden example");
expect(output).not.toContain("H2: Still hidden");
expect(output).toContain("H1: Visible after close");
});
it.each([
["spaces", "~~~", "~~~ "],
["tabs", "```", "```\t\t"],
["a longer marker", "~~~", "~~~~~ \t"],
])("resumes headings after a valid closing fence with %s", (_name, opening, closing) => {
const tempRepoRoot = makeTempRepoRoot("openclaw-docs-fence-close-");
mkdirSync(path.join(tempRepoRoot, "docs"));
writeFileSync(
path.join(tempRepoRoot, "docs", "page.md"),
`${opening}\n# Hidden example\n${closing}\n# Visible after close\n`,
);
const output = renderDocsHeadingMap(path.join(tempRepoRoot, "docs"));
expect(output).not.toContain("H1: Hidden example");
expect(output).toContain("H1: Visible after close");
});
it("normalizes injected Windows paths for nested page routes", () => {
const tempRepoRoot = makeTempRepoRoot("openclaw-docs-headings-windows-");
const docsDir = path.join(tempRepoRoot, "docs");
mkdirSync(path.join(docsDir, "nested"), { recursive: true });
writeFileSync(path.join(docsDir, "nested", "index.mdx"), "# Nested index\n");
writeFileSync(path.join(docsDir, "nested", "page.md"), "# Nested page\n");
const output = renderDocsHeadingMap(docsDir, {
relativePath: (base, fullPath) => path.relative(base, fullPath).replace(/[\\/]+/gu, "\\"),
});
expect(output).toContain("## nested/index.mdx\n\n- Route: /nested");
expect(output).toContain("## nested/page.md\n\n- Route: /nested/page");
expect(output).not.toContain("\\");
});
});