Files
vercel__vercel-plugin/tests/build-from-skills.test.ts
MelkeyDev 6e51924cb2 [FIX] Removing old tests (#90)
* removing skills from removed skills

* removing old tests

* fixing plugin version mishandling

* tests working

* fixing broken test CI fixture
2026-05-19 09:50:25 -07:00

548 lines
19 KiB
TypeScript

import { describe, test, expect, beforeEach, afterAll } from "bun:test";
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
import { join, resolve } from "node:path";
import { tmpdir } from "node:os";
import {
extractSkillSection,
resolveIncludes,
loadSkillContent,
clearSkillCache,
compileTemplate,
DiagnosticCode,
} from "../scripts/build-from-skills.ts";
import type { CompileResult, CompileDiagnostic, ResolvedInclude } from "../scripts/build-from-skills.ts";
const ROOT = resolve(import.meta.dirname, "..");
const SKILLS_DIR = join(ROOT, "skills");
// ---------------------------------------------------------------------------
// Fixture: temporary skill directory for isolated tests
// ---------------------------------------------------------------------------
const TMP_ROOT = join(tmpdir(), `build-from-skills-test-${Date.now()}`);
const TMP_SKILLS = join(TMP_ROOT, "skills");
function writeFixtureSkill(name: string, content: string): void {
const dir = join(TMP_SKILLS, name);
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "SKILL.md"), content);
}
beforeEach(() => {
clearSkillCache();
});
afterAll(() => {
rmSync(TMP_ROOT, { recursive: true, force: true });
});
// Set up fixtures once
mkdirSync(TMP_SKILLS, { recursive: true });
writeFixtureSkill(
"test-skill",
`---
name: test-skill
description: A test skill for unit tests
summary: Test skill summary
metadata:
priority: 7
---
# Test Skill
Introduction paragraph.
## Installation
\`\`\`bash
npm install test-skill
\`\`\`
## Configuration
Set up your config file:
\`\`\`json
{ "key": "value" }
\`\`\`
### Advanced Configuration
For advanced users, configure these options:
- Option A
- Option B
## Usage
Use the skill like this.
`,
);
writeFixtureSkill(
"nested-skill",
`---
name: nested-skill
description: Skill with nested headings
summary: Nested summary
metadata:
priority: 5
---
# Nested Skill
## Core Functions
Overview of core functions.
### Text Generation
Generate text with this API.
\`\`\`ts
const text = await generate("hello");
\`\`\`
### Image Generation
Generate images with this API.
## Other Section
Other content here.
`,
);
// ---------------------------------------------------------------------------
// extractSkillSection
// ---------------------------------------------------------------------------
describe("extractSkillSection", () => {
test("extracts a top-level section by heading text", () => {
const result = extractSkillSection("test-skill", "Installation", TMP_SKILLS);
expect(result).toContain("npm install test-skill");
expect(result).not.toContain("## Configuration");
});
test("extracts section with explicit ## prefix", () => {
const result = extractSkillSection("test-skill", "## Installation", TMP_SKILLS);
expect(result).toContain("npm install test-skill");
});
test("extracts section including nested headings", () => {
const result = extractSkillSection("test-skill", "Configuration", TMP_SKILLS);
expect(result).toContain("Set up your config file");
expect(result).toContain("### Advanced Configuration");
expect(result).toContain("Option A");
// Should stop before next ## heading
expect(result).not.toContain("## Usage");
});
test("extracts a sub-section directly", () => {
const result = extractSkillSection("test-skill", "Advanced Configuration", TMP_SKILLS);
expect(result).toContain("Option A");
expect(result).not.toContain("Set up your config file");
});
test("extracts nested section via > path", () => {
const result = extractSkillSection(
"nested-skill",
"Core Functions > Text Generation",
TMP_SKILLS,
);
expect(result).toContain("Generate text with this API");
expect(result).not.toContain("Image Generation");
});
test("heading matching is case-insensitive", () => {
const result = extractSkillSection("test-skill", "installation", TMP_SKILLS);
expect(result).toContain("npm install test-skill");
});
test("returns empty string for non-existent heading", () => {
const result = extractSkillSection("test-skill", "Nonexistent Heading", TMP_SKILLS);
expect(result).toBe("");
});
test("throws for non-existent skill", () => {
expect(() =>
extractSkillSection("no-such-skill", "Installation", TMP_SKILLS),
).toThrow(/not found/);
});
test("works with real skills directory", () => {
// ai-sdk has a real "Prerequisites" section
const result = extractSkillSection("ai-sdk", "Prerequisites", SKILLS_DIR);
expect(result).toContain("`ai` package");
});
});
// ---------------------------------------------------------------------------
// resolveIncludes — section includes
// ---------------------------------------------------------------------------
describe("resolveIncludes", () => {
test("resolves a section include marker", () => {
const template = `# My Agent\n\n{{include:skill:test-skill:Installation}}\n\nDone.`;
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toContain("npm install test-skill");
expect(result).toContain("# My Agent");
expect(result).toContain("Done.");
expect(result).not.toContain("{{include:");
});
test("resolves multiple include markers", () => {
const template = [
"{{include:skill:test-skill:Installation}}",
"---",
"{{include:skill:test-skill:Usage}}",
].join("\n");
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toContain("npm install test-skill");
expect(result).toContain("Use the skill like this");
});
test("resolves nested heading path", () => {
const template = "{{include:skill:nested-skill:Core Functions > Text Generation}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toContain("Generate text with this API");
expect(result).not.toContain("Image Generation");
});
test("throws on unresolved section include in strict mode", () => {
const template = "{{include:skill:test-skill:Nonexistent}}";
expect(() =>
resolveIncludes(template, { skillsDir: TMP_SKILLS, strict: true }),
).toThrow(/Unresolved include/);
});
test("throws on missing skill in strict mode", () => {
const template = "{{include:skill:no-such-skill:Installation}}";
expect(() =>
resolveIncludes(template, { skillsDir: TMP_SKILLS, strict: true }),
).toThrow(/Unresolved include/);
});
test("inserts HTML comment for unresolved includes in non-strict mode", () => {
const template = "Before {{include:skill:test-skill:Nonexistent}} After";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, strict: false });
expect(result).toContain("<!-- Unresolved include");
expect(result).toContain("Before");
expect(result).toContain("After");
});
});
// ---------------------------------------------------------------------------
// resolveIncludes — frontmatter includes
// ---------------------------------------------------------------------------
describe("resolveIncludes — frontmatter", () => {
test("resolves a frontmatter string field", () => {
const template = "Skill: {{include:skill:test-skill:frontmatter:description}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toBe("Skill: A test skill for unit tests");
});
test("resolves a frontmatter nested field (metadata.priority)", () => {
const template = "Priority: {{include:skill:test-skill:frontmatter:metadata.priority}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toBe("Priority: 7");
});
test("resolves frontmatter name field", () => {
const template = "{{include:skill:test-skill:frontmatter:name}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toBe("test-skill");
});
test("resolves frontmatter summary field", () => {
const template = "{{include:skill:test-skill:frontmatter:summary}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(result).toBe("Test skill summary");
});
test("throws on missing frontmatter field in strict mode", () => {
const template = "{{include:skill:test-skill:frontmatter:nonexistent}}";
expect(() =>
resolveIncludes(template, { skillsDir: TMP_SKILLS, strict: true }),
).toThrow(/Unresolved include/);
});
});
// ---------------------------------------------------------------------------
// loadSkillContent
// ---------------------------------------------------------------------------
describe("loadSkillContent", () => {
test("loads and caches skill content", () => {
const first = loadSkillContent("test-skill", TMP_SKILLS);
const second = loadSkillContent("test-skill", TMP_SKILLS);
expect(first).toBe(second); // Same reference (cached)
expect(first.body).toContain("# Test Skill");
});
test("throws for missing skill", () => {
expect(() => loadSkillContent("nonexistent", TMP_SKILLS)).toThrow(/not found/);
});
});
// ---------------------------------------------------------------------------
// Error message quality
// ---------------------------------------------------------------------------
describe("error messages", () => {
test("unresolved include error includes the full marker", () => {
const template = "{{include:skill:test-skill:Missing Section}}";
try {
resolveIncludes(template, { skillsDir: TMP_SKILLS });
expect(true).toBe(false); // Should not reach here
} catch (e) {
const msg = (e as Error).message;
expect(msg).toContain("include:skill:test-skill:Missing Section");
expect(msg).toContain("Heading");
}
});
test("missing skill error includes file path", () => {
try {
extractSkillSection("no-such-skill", "foo", TMP_SKILLS);
expect(true).toBe(false);
} catch (e) {
const msg = (e as Error).message;
expect(msg).toContain("no-such-skill");
expect(msg).toContain("does not exist");
}
});
});
// ---------------------------------------------------------------------------
// DiagnosticCode enum stability
// ---------------------------------------------------------------------------
describe("DiagnosticCode", () => {
test("has exactly four stable string codes", () => {
expect(DiagnosticCode.SKILL_NOT_FOUND).toBe("SKILL_NOT_FOUND");
expect(DiagnosticCode.HEADING_NOT_FOUND).toBe("HEADING_NOT_FOUND");
expect(DiagnosticCode.FRONTMATTER_NOT_FOUND).toBe("FRONTMATTER_NOT_FOUND");
expect(DiagnosticCode.STALE_OUTPUT).toBe("STALE_OUTPUT");
expect(Object.keys(DiagnosticCode)).toHaveLength(4);
});
});
// ---------------------------------------------------------------------------
// resolveIncludes — structured mode (CompileResult)
// ---------------------------------------------------------------------------
describe("resolveIncludes — structured mode", () => {
test("returns CompileResult when structured: true", () => {
const template = "# Agent\n\n{{include:skill:test-skill:Installation}}\n\nDone.";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
// Must be a CompileResult object, not a string
expect(typeof result).toBe("object");
expect(typeof result.output).toBe("string");
expect(Array.isArray(result.resolved)).toBe(true);
expect(Array.isArray(result.diagnostics)).toBe(true);
expect(Array.isArray(result.dependencies)).toBe(true);
});
test("resolved includes track provenance", () => {
const template = "{{include:skill:test-skill:Installation}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.resolved).toHaveLength(1);
const inc = result.resolved[0];
expect(inc.marker).toBe("{{include:skill:test-skill:Installation}}");
expect(inc.skillName).toBe("test-skill");
expect(inc.target).toBe("Installation");
expect(inc.type).toBe("section");
expect(inc.contentLength).toBeGreaterThan(0);
});
test("frontmatter includes track provenance with type frontmatter", () => {
const template = "{{include:skill:test-skill:frontmatter:description}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.resolved).toHaveLength(1);
expect(result.resolved[0].type).toBe("frontmatter");
expect(result.resolved[0].target).toBe("frontmatter:description");
});
test("dependencies list all referenced skills", () => {
const template = [
"{{include:skill:test-skill:Installation}}",
"{{include:skill:nested-skill:Core Functions > Text Generation}}",
].join("\n");
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.dependencies).toContain("test-skill");
expect(result.dependencies).toContain("nested-skill");
expect(result.dependencies).toHaveLength(2);
});
test("deduplicates dependencies", () => {
const template = [
"{{include:skill:test-skill:Installation}}",
"{{include:skill:test-skill:Usage}}",
].join("\n");
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.dependencies).toEqual(["test-skill"]);
});
test("unresolved includes produce diagnostics with HEADING_NOT_FOUND code", () => {
const template = "{{include:skill:test-skill:Nonexistent}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.diagnostics).toHaveLength(1);
expect(result.diagnostics[0].code).toBe(DiagnosticCode.HEADING_NOT_FOUND);
expect(result.diagnostics[0].marker).toBe("{{include:skill:test-skill:Nonexistent}}");
expect(result.diagnostics[0].suggestion).toBeTruthy();
});
test("missing skill produces SKILL_NOT_FOUND diagnostic", () => {
const template = "{{include:skill:no-such-skill:Installation}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.diagnostics).toHaveLength(1);
expect(result.diagnostics[0].code).toBe(DiagnosticCode.SKILL_NOT_FOUND);
expect(result.diagnostics[0].suggestion).toContain("skills/no-such-skill/SKILL.md");
});
test("missing frontmatter field produces FRONTMATTER_NOT_FOUND diagnostic", () => {
const template = "{{include:skill:test-skill:frontmatter:nonexistent}}";
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.diagnostics).toHaveLength(1);
expect(result.diagnostics[0].code).toBe(DiagnosticCode.FRONTMATTER_NOT_FOUND);
});
test("structured mode does NOT throw on errors (collects diagnostics)", () => {
const template = "Before {{include:skill:no-such-skill:foo}} After";
// Should not throw even with strict not set (structured always collects)
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.diagnostics.length).toBeGreaterThan(0);
expect(result.output).toContain("Before");
expect(result.output).toContain("After");
});
test("mixed resolved and unresolved includes", () => {
const template = [
"{{include:skill:test-skill:Installation}}",
"{{include:skill:test-skill:Nonexistent}}",
].join("\n");
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
expect(result.resolved).toHaveLength(1);
expect(result.diagnostics).toHaveLength(1);
expect(result.dependencies).toEqual(["test-skill"]);
});
});
// ---------------------------------------------------------------------------
// CompileResult shape contract (snapshot)
// ---------------------------------------------------------------------------
describe("CompileResult shape contract", () => {
test("successful resolution shape snapshot", () => {
const template = [
"# Agent",
"",
"{{include:skill:test-skill:Installation}}",
"",
"Summary: {{include:skill:test-skill:frontmatter:description}}",
].join("\n");
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
// Snapshot the shape with stable fields only (strip variable content lengths)
const shape = {
hasOutput: typeof result.output === "string" && result.output.length > 0,
resolvedCount: result.resolved.length,
resolved: result.resolved.map((r) => ({
marker: r.marker,
skillName: r.skillName,
target: r.target,
type: r.type,
hasContentLength: r.contentLength > 0,
hasLineNumber: typeof r.lineNumber === "number" && r.lineNumber > 0,
})),
diagnosticCount: result.diagnostics.length,
dependencies: result.dependencies,
};
expect(shape).toMatchSnapshot();
});
test("error diagnostic shape snapshot", () => {
const template = [
"{{include:skill:no-such-skill:Installation}}",
"{{include:skill:test-skill:Nonexistent}}",
"{{include:skill:test-skill:frontmatter:nonexistent}}",
].join("\n");
const result = resolveIncludes(template, { skillsDir: TMP_SKILLS, structured: true });
const shape = {
resolvedCount: result.resolved.length,
diagnostics: result.diagnostics.map((d) => ({
code: d.code,
marker: d.marker,
hasSuggestion: typeof d.suggestion === "string" && d.suggestion.length > 0,
})),
dependencies: result.dependencies.sort(),
};
expect(shape).toMatchSnapshot();
});
});
// ---------------------------------------------------------------------------
// compileTemplate
// ---------------------------------------------------------------------------
describe("compileTemplate", () => {
const TMP_TEMPLATES = join(TMP_ROOT, "templates");
function writeFixtureTemplate(name: string, content: string): string {
mkdirSync(TMP_TEMPLATES, { recursive: true });
const p = join(TMP_TEMPLATES, name);
writeFileSync(p, content);
return p;
}
test("returns a CompileResult for a valid template", () => {
const tmpl = writeFixtureTemplate(
"valid.md.tmpl",
"# Agent\n\n{{include:skill:test-skill:Installation}}\n",
);
const result = compileTemplate(tmpl, { skillsDir: TMP_SKILLS });
expect(result.output).toContain("npm install test-skill");
expect(result.resolved).toHaveLength(1);
expect(result.diagnostics).toHaveLength(0);
expect(result.dependencies).toEqual(["test-skill"]);
});
test("collects diagnostics for unresolved includes without throwing", () => {
const tmpl = writeFixtureTemplate(
"broken.md.tmpl",
"{{include:skill:no-such-skill:foo}}\n",
);
const result = compileTemplate(tmpl, { skillsDir: TMP_SKILLS });
expect(result.diagnostics).toHaveLength(1);
expect(result.diagnostics[0].code).toBe(DiagnosticCode.SKILL_NOT_FOUND);
expect(result.dependencies).toEqual(["no-such-skill"]);
});
test("handles template with no includes", () => {
const tmpl = writeFixtureTemplate("plain.md.tmpl", "# Just text\n\nNo includes here.\n");
const result = compileTemplate(tmpl, { skillsDir: TMP_SKILLS });
expect(result.output).toBe("# Just text\n\nNo includes here.\n");
expect(result.resolved).toHaveLength(0);
expect(result.diagnostics).toHaveLength(0);
expect(result.dependencies).toHaveLength(0);
});
});