import { describe, expect, it } from "vitest"; import type { SkillStatusEntry } from "@/lib/skills/types"; import { deriveAgentSkillDisplayState, buildAgentSkillsAllowlistSet, buildSkillMissingDetails, buildSkillReasons, canRemoveSkill, deriveAgentSkillsAccessMode, deriveSkillReadinessState, filterOsCompatibleSkills, groupSkillsBySource, hasInstallableMissingBinary, isBundledBlockedSkill, isSkillOsIncompatible, normalizeAgentSkillsAllowlist, resolvePreferredInstallOption, } from "@/lib/skills/presentation"; const createSkill = (overrides: Partial): SkillStatusEntry => ({ name: "skill", description: "", source: "openclaw-workspace", bundled: false, filePath: "/tmp/workspace/skill/SKILL.md", baseDir: "/tmp/workspace/skill", skillKey: "skill", always: false, disabled: false, blockedByAllowlist: false, eligible: true, requirements: { bins: [], anyBins: [], env: [], config: [], os: [] }, missing: { bins: [], anyBins: [], env: [], config: [], os: [] }, configChecks: [], install: [], ...overrides, }); describe("skills presentation helpers", () => { it("groups skills by source with stable ordering", () => { const groups = groupSkillsBySource([ createSkill({ name: "other", source: "custom-source" }), createSkill({ name: "installed", source: "openclaw-managed" }), createSkill({ name: "workspace", source: "openclaw-workspace" }), createSkill({ name: "bundled", source: "openclaw-bundled", bundled: true }), createSkill({ name: "extra", source: "openclaw-extra" }), ]); expect(groups.map((group) => group.id)).toEqual([ "workspace", "built-in", "installed", "extra", "other", ]); expect(groups[0]?.skills.map((skill) => skill.name)).toEqual(["workspace"]); expect(groups[1]?.skills.map((skill) => skill.name)).toEqual(["bundled"]); }); it("builds explicit missing detail lines", () => { const details = buildSkillMissingDetails( createSkill({ eligible: false, missing: { bins: ["playwright"], anyBins: ["chromium", "chrome"], env: ["GITHUB_TOKEN"], config: ["browser.enabled"], os: ["linux"], }, }) ); expect(details).toEqual([ "Missing tools: playwright", "Missing one-of tools (install any): chromium | chrome", "Missing env vars (set in gateway env): GITHUB_TOKEN", "Missing config values (set in openclaw.json): browser.enabled", "Requires OS: Linux", ]); }); it("builds reasons from policy and missing requirements", () => { const reasons = buildSkillReasons( createSkill({ eligible: false, disabled: true, blockedByAllowlist: true, missing: { bins: ["playwright"], anyBins: [], env: [], config: [], os: [], }, }) ); expect(reasons).toEqual(["disabled", "blocked by allowlist", "missing tools"]); }); it("detects_os_incompatibility_from_missing_os_requirements", () => { expect( isSkillOsIncompatible( createSkill({ missing: { bins: [], anyBins: [], env: [], config: [], os: ["darwin"], }, }) ) ).toBe(true); expect( isSkillOsIncompatible( createSkill({ missing: { bins: [], anyBins: [], env: [], config: [], os: [" "], }, }) ) ).toBe(false); }); it("filters_out_os_incompatible_skills_while_preserving_order", () => { const filtered = filterOsCompatibleSkills([ createSkill({ name: "github", missing: { bins: [], anyBins: [], env: [], config: [], os: [] } }), createSkill({ name: "apple-notes", missing: { bins: [], anyBins: [], env: [], config: [], os: ["darwin"] }, }), createSkill({ name: "slack", missing: { bins: [], anyBins: [], env: [], config: [], os: [] } }), ]); expect(filtered.map((skill) => skill.name)).toEqual(["github", "slack"]); }); it("detects bundled blocked skills", () => { expect( isBundledBlockedSkill( createSkill({ source: "openclaw-bundled", bundled: true, eligible: false, }) ) ).toBe(true); expect(isBundledBlockedSkill(createSkill({ bundled: true, eligible: true }))).toBe(false); }); it("detects installable missing binaries including anyBins overlap", () => { const skill = createSkill({ eligible: false, missing: { bins: [], anyBins: ["chromium", "chrome"], env: [], config: [], os: [], }, install: [ { id: "install-chromium", kind: "download", label: "Install chromium", bins: ["chromium"], }, ], }); expect(hasInstallableMissingBinary(skill)).toBe(true); expect(resolvePreferredInstallOption(skill)?.id).toBe("install-chromium"); }); it("selects_install_option_that_matches_missing_bins", () => { const skill = createSkill({ eligible: false, missing: { bins: ["gh"], anyBins: [], env: [], config: [], os: [], }, install: [ { id: "install-other", kind: "download", label: "Install other tool", bins: ["other"], }, { id: "install-gh", kind: "brew", label: "Install gh", bins: ["gh"], }, ], }); expect(resolvePreferredInstallOption(skill)?.id).toBe("install-gh"); }); it("marks only gateway-managed and workspace skill sources as removable", () => { expect(canRemoveSkill(createSkill({ source: "openclaw-managed" }))).toBe(true); expect(canRemoveSkill(createSkill({ source: "openclaw-workspace" }))).toBe(true); expect(canRemoveSkill(createSkill({ source: "agents-skills-personal" }))).toBe(false); expect(canRemoveSkill(createSkill({ source: "agents-skills-project" }))).toBe(false); expect(canRemoveSkill(createSkill({ source: "openclaw-bundled", bundled: true }))).toBe( false ); expect(canRemoveSkill(createSkill({ source: "openclaw-extra" }))).toBe(false); }); it("derives agent access mode from allowlist shape", () => { expect(deriveAgentSkillsAccessMode(undefined)).toBe("all"); expect(deriveAgentSkillsAccessMode([])).toBe("none"); expect(deriveAgentSkillsAccessMode([" ", "github"])).toBe("selected"); }); it("normalizes allowlist values and creates a lookup set", () => { expect(normalizeAgentSkillsAllowlist([" github ", "github", "slack", " "])).toEqual([ "github", "slack", ]); expect(buildAgentSkillsAllowlistSet([" github ", "slack"]).has("github")).toBe(true); expect(buildAgentSkillsAllowlistSet([" github ", "slack"]).has("browser")).toBe(false); }); it("classifies readiness with disabled and unavailable precedence", () => { expect(deriveSkillReadinessState(createSkill({ disabled: true, eligible: false }))).toBe( "disabled-globally" ); expect( deriveSkillReadinessState( createSkill({ eligible: false, missing: { bins: [], anyBins: [], env: [], config: [], os: ["darwin"] }, }) ) ).toBe("unavailable"); expect( deriveSkillReadinessState( createSkill({ eligible: false, blockedByAllowlist: true, }) ) ).toBe("unavailable"); expect( deriveSkillReadinessState( createSkill({ eligible: false, missing: { bins: ["gh"], anyBins: [], env: [], config: [], os: [] }, }) ) ).toBe("needs-setup"); expect(deriveSkillReadinessState(createSkill({ eligible: true }))).toBe("ready"); }); it("maps readiness into agent display states", () => { expect(deriveAgentSkillDisplayState("ready")).toBe("ready"); expect(deriveAgentSkillDisplayState("needs-setup")).toBe("setup-required"); expect(deriveAgentSkillDisplayState("disabled-globally")).toBe("setup-required"); expect(deriveAgentSkillDisplayState("unavailable")).toBe("not-supported"); }); });