import { describe, expect, it, vi } from
"vitest" ;
import type { SkillStatusEntry, SkillStatusReport } from
"../agents/skills-status.js" ;
import { createEmptyInstallChecks } from
"./requirements-test-fixtures.js" ;
import { formatSkillInfo, formatSkillsCheck, formatSkillsList } from
"./skills-cli.format.js" ;
// Unit tests: don't pay the runtime cost of loading/parsing the real skills loader.
vi.mock(
"@mariozechner/pi-coding-agent" , () => ({
loadSkillsFromDir: () => ({ skills: [] }),
formatSkillsForPrompt: () =>
"" ,
}));
function createMockSkill(overrides: Partial<SkillStatusEntry> = {}): SkillStatusEntry {
return {
name:
"test-skill" ,
description:
"A test skill" ,
source:
"bundled" ,
bundled:
false ,
filePath:
"/path/to/SKILL.md" ,
baseDir:
"/path/to" ,
skillKey:
"test-skill" ,
emoji:
"" ,
homepage:
"https://example.com ",
always:
false ,
disabled:
false ,
blockedByAllowlist:
false ,
eligible:
true ,
...createEmptyInstallChecks(),
...overrides,
};
}
function createMockReport(skills: SkillStatusEntry[]): SkillStatusReport {
return {
workspaceDir:
"/workspace" ,
managedSkillsDir:
"/managed" ,
skills,
};
}
describe(
"skills-cli" , () => {
describe(
"formatSkillsList" , () => {
it(
"formats empty skills list" , () => {
const report = createMockReport([]);
const output = formatSkillsList(report, {});
expect(output).toContain(
"No skills found" );
expect(output).toContain(
"openclaw skills search" );
});
it(
"formats skills list with eligible skill" , () => {
const report = createMockReport([
createMockSkill({
name:
"peekaboo" ,
description:
"Capture UI screenshots" ,
emoji:
"" ,
eligible:
true ,
}),
]);
const output = formatSkillsList(report, {});
expect(output).toContain(
"peekaboo" );
expect(output).toContain(
"" );
expect(output).toContain(
"✓" );
});
it(
"formats skills list with disabled skill" , () => {
const report = createMockReport([
createMockSkill({
name:
"disabled-skill" ,
disabled:
true ,
eligible:
false ,
}),
]);
const output = formatSkillsList(report, {});
expect(output).toContain(
"disabled-skill" );
expect(output).toContain(
"disabled" );
});
it(
"formats skills list with missing requirements" , () => {
const report = createMockReport([
createMockSkill({
name:
"needs-stuff" ,
eligible:
false ,
missing: {
bins: [
"ffmpeg" ],
anyBins: [
"rg" ,
"grep" ],
env: [
"API_KEY" ],
config: [],
os: [
"darwin" ],
},
}),
]);
const output = formatSkillsList(report, { verbose:
true });
expect(output).toContain(
"needs-stuff" );
expect(output).toContain(
"needs setup" );
expect(output).toContain(
"anyBins" );
expect(output).toContain(
"os:" );
});
it(
"filters to eligible only with --eligible flag" , () => {
const report = createMockReport([
createMockSkill({ name:
"eligible-one" , eligible:
true }),
createMockSkill({
name:
"not-eligible" ,
eligible:
false ,
disabled:
true ,
}),
]);
const output = formatSkillsList(report, { eligible:
true });
expect(output).toContain(
"eligible-one" );
expect(output).not.toContain(
"not-eligible" );
});
});
describe(
"formatSkillInfo" , () => {
it(
"returns not found message for unknown skill" , () => {
const report = createMockReport([]);
const output = formatSkillInfo(report,
"unknown-skill" , {});
expect(output).toContain(
"not found" );
expect(output).toContain(
"openclaw skills install" );
});
it(
"shows detailed info for a skill" , () => {
const report = createMockReport([
createMockSkill({
name:
"detailed-skill" ,
description:
"A detailed description" ,
homepage:
"https://example.com ",
requirements: {
bins: [
"node" ],
anyBins: [
"rg" ,
"grep" ],
env: [
"API_KEY" ],
config: [],
os: [],
},
missing: {
bins: [],
anyBins: [],
env: [
"API_KEY" ],
config: [],
os: [],
},
}),
]);
const output = formatSkillInfo(report,
"detailed-skill" , {});
expect(output).toContain(
"detailed-skill" );
expect(output).toContain(
"A detailed description" );
expect(output).toContain(
"https://example.com ");
expect(output).toContain(
"node" );
expect(output).toContain(
"Any binaries" );
expect(output).toContain(
"API_KEY" );
});
it(
"shows API key storage guidance for the active config path" , () => {
const report = createMockReport([
createMockSkill({
name:
"env-aware-skill" ,
skillKey:
"env-aware-skill" ,
primaryEnv:
"API_KEY" ,
eligible:
false ,
requirements: {
bins: [],
anyBins: [],
env: [
"API_KEY" ],
config: [],
os: [],
},
missing: {
bins: [],
anyBins: [],
env: [
"API_KEY" ],
config: [],
os: [],
},
}),
]);
const output = formatSkillInfo(report,
"env-aware-skill" , {});
expect(output).toContain(
"OPENCLAW_CONFIG_PATH" );
expect(output).toContain(
"default: ~/.openclaw/openclaw.json" );
expect(output).toContain(
"skills.entries.env-aware-skill.apiKey" );
});
it(
"normalizes text-presentation emoji selectors in info output" , () => {
const report = createMockReport([
createMockSkill({
name:
"info-emoji" ,
emoji:
"\uFE0E" ,
}),
]);
const output = formatSkillInfo(report,
"info-emoji" , {});
expect(output).toContain(
"️" );
});
});
describe(
"formatSkillsCheck" , () => {
it(
"shows summary of skill status" , () => {
const report = createMockReport([
createMockSkill({ name:
"ready-1" , eligible:
true }),
createMockSkill({ name:
"ready-2" , eligible:
true }),
createMockSkill({
name:
"not-ready" ,
eligible:
false ,
missing: { bins: [
"go" ], anyBins: [], env: [], config: [], os: [] },
}),
createMockSkill({ name:
"disabled" , eligible:
false , disabled:
true }),
]);
const output = formatSkillsCheck(report, {});
expect(output).toContain(
"2" );
// eligible count
expect(output).toContain(
"ready-1" );
expect(output).toContain(
"ready-2" );
expect(output).toContain(
"not-ready" );
expect(output).toContain(
"go" );
// missing binary
expect(output).toContain(
"openclaw skills update" );
});
it(
"normalizes text-presentation emoji selectors in check output" , () => {
const report = createMockReport([
createMockSkill({ name:
"ready-emoji" , emoji:
"\uFE0E" , eligible:
true }),
createMockSkill({
name:
"missing-emoji" ,
emoji:
"\uFE0E" ,
eligible:
false ,
missing: { bins: [
"ffmpeg" ], anyBins: [], env: [], config: [], os: [] },
}),
]);
const output = formatSkillsCheck(report, {});
expect(output).toContain(
"️ ready-emoji" );
expect(output).toContain(
"️ missing-emoji" );
});
});
describe(
"JSON output" , () => {
it.each([
{
formatter:
"list" ,
output: formatSkillsList(createMockReport([createMockSkill({ name:
"json-skill" })])
, {
json: true ,
}),
assert : (parsed: Record<string, unknown>) => {
const skills = parsed.skills as Array<Record<string, unknown>>;
expect(skills).toHaveLength(1 );
expect(skills[0 ]?.name).toBe("json-skill" );
},
},
{
formatter: "info" ,
output: formatSkillInfo(
createMockReport([createMockSkill({ name: "info-skill" })]),
"info-skill" ,
{ json: true },
),
assert : (parsed: Record<string, unknown>) => {
expect(parsed.name).toBe("info-skill" );
},
},
{
formatter: "check" ,
output: formatSkillsCheck(
createMockReport([
createMockSkill({ name: "skill-1" , eligible: true }),
createMockSkill({ name: "skill-2" , eligible: false }),
]),
{ json: true },
),
assert : (parsed: Record<string, unknown>) => {
const summary = parsed.summary as Record<string, unknown>;
expect(summary.eligible).toBe(1 );
expect(summary.total).toBe(2 );
},
},
])("outputs JSON with --json flag for $formatter" , ({ output, assert }) => {
const parsed = JSON.parse(output) as Record<string, unknown>;
assert (parsed);
});
it("sanitizes ANSI and C1 controls in skills list JSON output" , () => {
const report = createMockReport([
createMockSkill({
name: "json-skill" ,
emoji: "\u001b[31m\u001b[0m\u009f" ,
description: "desc\u0093\u001b[2J\u001b[33m colored\u001b[0m" ,
}),
]);
const output = formatSkillsList(report, { json: true });
const parsed = JSON.parse(output) as {
skills: Array<{ emoji: string; description: string }>;
};
expect(parsed.skills[0 ]?.emoji).toBe("" );
expect(parsed.skills[0 ]?.description).toBe("desc colored" );
expect(output).not.toContain("\\u001b" );
});
it("sanitizes skills info JSON output" , () => {
const report = createMockReport([
createMockSkill({
name: "info-json" ,
emoji: "\u001b[31m\u001b[0m\u009f" ,
description: "hi\u0091" ,
homepage: "https://example.com/\u0092docs ",
}),
]);
const output = formatSkillInfo(report, "info-json" , { json: true });
const parsed = JSON.parse(output) as {
emoji: string;
description: string;
homepage: string;
};
expect(parsed.emoji).toBe("" );
expect(parsed.description).toBe("hi" );
expect(parsed.homepage).toBe("https://example.com/docs ");
});
});
});
Messung V0.5 in Prozent C=98 H=100 G=98
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland