180 lines
6.0 KiB
TypeScript
180 lines
6.0 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
parseFrontmatterMarkdown,
|
|
classifyInventoryKind,
|
|
deriveTrustLevel,
|
|
} from "@main/skill-format";
|
|
import type { FileInventoryEntry } from "@main/skill-types";
|
|
|
|
describe("skill-format", () => {
|
|
describe("parseFrontmatterMarkdown", () => {
|
|
it("parses standard frontmatter and body", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
"---\nname: test\nslug: test\n---\n# Body",
|
|
);
|
|
expect(result.frontmatter).toEqual({ name: "test", slug: "test" });
|
|
expect(result.body).toBe("# Body");
|
|
});
|
|
|
|
it("returns empty frontmatter for text without frontmatter", () => {
|
|
const result = parseFrontmatterMarkdown("No frontmatter");
|
|
expect(result.frontmatter).toEqual({});
|
|
expect(result.body).toBe("No frontmatter");
|
|
});
|
|
|
|
it("handles frontmatter with no body", () => {
|
|
const result = parseFrontmatterMarkdown("---\nname: test\n---");
|
|
expect(result.frontmatter).toEqual({ name: "test" });
|
|
expect(result.body).toBe("");
|
|
});
|
|
|
|
it("normalizes CRLF to LF before parsing", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
"---\r\nname: test\r\n---\r\n# Body",
|
|
);
|
|
expect(result.frontmatter).toEqual({ name: "test" });
|
|
expect(result.body).toBe("# Body");
|
|
});
|
|
|
|
it("handles quoted string values", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
'---\nname: "My Skill"\n---\n',
|
|
);
|
|
expect(result.frontmatter.name).toBe("My Skill");
|
|
});
|
|
|
|
it("parses boolean values", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
"---\nenabled: true\ndisabled: false\n---\n",
|
|
);
|
|
expect(result.frontmatter.enabled).toBe(true);
|
|
expect(result.frontmatter.disabled).toBe(false);
|
|
});
|
|
|
|
it("parses numeric values", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
"---\nversion: 42\nprecision: 3.14\n---\n",
|
|
);
|
|
expect(result.frontmatter.version).toBe(42);
|
|
expect(result.frontmatter.precision).toBe(3.14);
|
|
});
|
|
|
|
it("parses null values", () => {
|
|
const result = parseFrontmatterMarkdown("---\nvalue: null\n---\n");
|
|
expect(result.frontmatter.value).toBeNull();
|
|
});
|
|
|
|
it("handles empty arrays and objects", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
"---\nitems: []\nopts: {}\n---\n",
|
|
);
|
|
expect(result.frontmatter.items).toEqual([]);
|
|
expect(result.frontmatter.opts).toEqual({});
|
|
});
|
|
|
|
it("handles body with multiple lines", () => {
|
|
const result = parseFrontmatterMarkdown(
|
|
"---\nname: test\n---\nLine 1\nLine 2\nLine 3",
|
|
);
|
|
expect(result.body).toBe("Line 1\nLine 2\nLine 3");
|
|
});
|
|
});
|
|
|
|
describe("classifyInventoryKind", () => {
|
|
it("classifies SKILL.md as skill", () => {
|
|
expect(classifyInventoryKind("SKILL.md")).toBe("skill");
|
|
});
|
|
|
|
it("classifies nested SKILL.md as skill", () => {
|
|
expect(classifyInventoryKind("subdir/SKILL.md")).toBe("skill");
|
|
});
|
|
|
|
it("classifies references/ path as reference", () => {
|
|
expect(classifyInventoryKind("references/api.md")).toBe("reference");
|
|
});
|
|
|
|
it("classifies scripts/ path as script", () => {
|
|
expect(classifyInventoryKind("scripts/build.sh")).toBe("script");
|
|
});
|
|
|
|
it("classifies assets/ path as asset", () => {
|
|
expect(classifyInventoryKind("assets/logo.png")).toBe("asset");
|
|
});
|
|
|
|
it("classifies .md files as markdown", () => {
|
|
expect(classifyInventoryKind("docs/guide.md")).toBe("markdown");
|
|
});
|
|
|
|
it("classifies config.json as other", () => {
|
|
expect(classifyInventoryKind("config.json")).toBe("other");
|
|
});
|
|
|
|
it("classifies .py files as script", () => {
|
|
expect(classifyInventoryKind("helper.py")).toBe("script");
|
|
});
|
|
|
|
it("classifies .sh files as script", () => {
|
|
expect(classifyInventoryKind("run.sh")).toBe("script");
|
|
});
|
|
|
|
it("classifies .js files as script", () => {
|
|
expect(classifyInventoryKind("index.js")).toBe("script");
|
|
});
|
|
|
|
it("classifies .ts files as script", () => {
|
|
expect(classifyInventoryKind("main.ts")).toBe("script");
|
|
});
|
|
|
|
it("classifies image files as asset", () => {
|
|
expect(classifyInventoryKind("photo.jpg")).toBe("asset");
|
|
expect(classifyInventoryKind("icon.svg")).toBe("asset");
|
|
expect(classifyInventoryKind("banner.webp")).toBe("asset");
|
|
expect(classifyInventoryKind("doc.pdf")).toBe("asset");
|
|
});
|
|
|
|
it("handles case insensitivity", () => {
|
|
expect(classifyInventoryKind("Skill.MD")).toBe("skill");
|
|
expect(classifyInventoryKind("References/API.md")).toBe("reference");
|
|
});
|
|
});
|
|
|
|
describe("deriveTrustLevel", () => {
|
|
it("returns scripts_executables when any script file exists", () => {
|
|
const inventory: FileInventoryEntry[] = [
|
|
{ path: "SKILL.md", kind: "skill" },
|
|
{ path: "scripts/build.sh", kind: "script" },
|
|
];
|
|
expect(deriveTrustLevel(inventory)).toBe("scripts_executables");
|
|
});
|
|
|
|
it("returns assets when asset but no script exists", () => {
|
|
const inventory: FileInventoryEntry[] = [
|
|
{ path: "SKILL.md", kind: "skill" },
|
|
{ path: "assets/logo.png", kind: "asset" },
|
|
];
|
|
expect(deriveTrustLevel(inventory)).toBe("assets");
|
|
});
|
|
|
|
it("returns assets when other but no script exists", () => {
|
|
const inventory: FileInventoryEntry[] = [
|
|
{ path: "SKILL.md", kind: "skill" },
|
|
{ path: "config.json", kind: "other" },
|
|
];
|
|
expect(deriveTrustLevel(inventory)).toBe("assets");
|
|
});
|
|
|
|
it("returns markdown_only when only markdown and skill files exist", () => {
|
|
const inventory: FileInventoryEntry[] = [
|
|
{ path: "SKILL.md", kind: "skill" },
|
|
{ path: "references/api.md", kind: "reference" },
|
|
{ path: "docs/guide.md", kind: "markdown" },
|
|
];
|
|
expect(deriveTrustLevel(inventory)).toBe("markdown_only");
|
|
});
|
|
|
|
it("returns markdown_only for empty inventory", () => {
|
|
expect(deriveTrustLevel([])).toBe("markdown_only");
|
|
});
|
|
});
|
|
});
|