mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
192 lines
7.1 KiB
TypeScript
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>
|
|
|
|
#### \`\`
|
|
|
|
\`\`\`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[*]` <script>alert(1)</script>");
|
|
expect(output).toContain(" - H3: <scr<script>ipt>alert(1)</script>");
|
|
expect(output).toContain(" - H4: ``");
|
|
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("\\");
|
|
});
|
|
});
|