/* @vitest-environment jsdom */
import { render } from
"lit" ;
import { afterEach, describe, expect, it, vi } from
"vitest" ;
import type { SkillStatusEntry, SkillStatusReport } from
"../types.ts" ;
import { renderSkills, type SkillsProps } from
"./skills.ts" ;
const dialogRestores: Array<() =>
void > = [];
function normalizeText(node: Element | DocumentFragment): string {
return node.textContent?.replace(/\s+/g,
" " ).trim() ??
"" ;
}
function createSkill(overrides: Partial<SkillStatusEntry> = {}): SkillStatusEntry {
return {
name:
"Repo Skill" ,
description:
"Skill description" ,
source:
"workspace" ,
filePath:
"/tmp/skill" ,
baseDir:
"/tmp" ,
skillKey:
"repo-skill" ,
bundled:
false ,
primaryEnv:
"OPENAI_API_KEY" ,
emoji: undefined,
homepage:
"https://example.com ",
always:
false ,
disabled:
false ,
blockedByAllowlist:
false ,
eligible:
true ,
requirements: {
bins: [],
env: [],
config: [],
os: [],
},
missing: {
bins: [],
env: [],
config: [],
os: [],
},
configChecks: [],
install: [],
...overrides,
};
}
function createProps(overrides: Partial<SkillsProps> = {}): SkillsProps {
const report: SkillStatusReport = {
workspaceDir:
"/tmp/workspace" ,
managedSkillsDir:
"/tmp/skills" ,
skills: [createSkill()],
};
return {
connected:
true ,
loading:
false ,
report,
error:
null ,
filter:
"" ,
statusFilter:
"all" ,
edits: {},
busyKey:
null ,
messages: {},
detailKey:
null ,
clawhubQuery:
"" ,
clawhubResults:
null ,
clawhubSearchLoading:
false ,
clawhubSearchError:
null ,
clawhubDetail:
null ,
clawhubDetailSlug:
null ,
clawhubDetailLoading:
false ,
clawhubDetailError:
null ,
clawhubInstallSlug:
null ,
clawhubInstallMessage:
null ,
onFilterChange: () => undefined,
onStatusFilterChange: () => undefined,
onRefresh: () => undefined,
onToggle: () => undefined,
onEdit: () => undefined,
onSaveKey: () => undefined,
onInstall: () => undefined,
onDetailOpen: () => undefined,
onDetailClose: () => undefined,
onClawHubQueryChange: () => undefined,
onClawHubDetailOpen: () => undefined,
onClawHubDetailClose: () => undefined,
onClawHubInstall: () => undefined,
...overrides,
};
}
describe(
"renderSkills" , () => {
afterEach(() => {
vi.restoreAllMocks();
while (dialogRestores.length >
0 ) {
dialogRestores.pop()?.();
}
});
it(
"opens detail dialogs and routes ClawHub actions" , async () => {
const container = document.createElement(
"div" );
const onDetailClose = vi.fn();
const showModal = vi.fn(
function (
this : HTMLDialogElement) {
this .setAttribute(
"open" ,
"" );
});
const onClawHubDetailOpen = vi.fn();
const onClawHubInstall = vi.fn();
installDialogMethod(
"showModal" , showModal);
installDialogMethod(
"close" ,
function (
this : HTMLDialogElement) {
this .removeAttribute(
"open" );
this .dispatchEvent(
new Event(
"close" ));
});
render(
renderSkills(
createProps({
detailKey:
"repo-skill" ,
onDetailClose,
}),
),
container,
);
await Promise.resolve();
expect(showModal).toHaveBeenCalledTimes(
1 );
expect(container.querySelector(
"dialog" )?.hasAttribute(
"open" )).toBe(
true );
container.querySelector<HTMLButtonElement>(
".md-preview-dialog__header .btn" )?.clic
k();
expect(onDetailClose).toHaveBeenCalledTimes(1 );
render(
renderSkills(
createProps({
clawhubQuery: "git" ,
clawhubResults: [
{
score: 0 .95 ,
slug: "github" ,
displayName: "GitHub" ,
summary: "GitHub integration for OpenClaw" ,
version: "1.2.3" ,
},
],
onClawHubDetailOpen,
onClawHubInstall,
}),
),
container,
);
await Promise.resolve();
let text = normalizeText(container);
expect(text).toContain("GitHub" );
expect(text).toContain("GitHub integration for OpenClaw" );
expect(text).toContain("v1.2.3" );
container.querySelector<HTMLElement>(".list-item" )?.click();
container
.querySelector<HTMLButtonElement>(".list-item .btn.btn--sm" )
?.dispatchEvent(new MouseEvent("click" , { bubbles: true }));
expect(onClawHubDetailOpen).toHaveBeenCalledTimes(1 );
expect(onClawHubDetailOpen).toHaveBeenCalledWith("github" );
expect(onClawHubInstall).toHaveBeenCalledTimes(1 );
expect(onClawHubInstall).toHaveBeenCalledWith("github" );
onClawHubInstall.mockClear();
showModal.mockClear();
render(
renderSkills(
createProps({
clawhubSearchError: "rate limited" ,
clawhubInstallMessage: { kind: "success" , text: "Installed github" },
clawhubDetailSlug: "github" ,
clawhubDetail: {
skill: {
slug: "github" ,
displayName: "GitHub" ,
summary: "GitHub integration for OpenClaw" ,
createdAt: 1 _700 _000 _000 ,
updatedAt: 1 _700 _000 _100 ,
},
latestVersion: {
version: "1.2.3" ,
createdAt: 1 _700 _000 _200 ,
changelog: "Added search support" ,
},
metadata: {
os: ["macos" , "linux" ],
},
owner: {
displayName: "OpenClaw" ,
handle: "openclaw" ,
},
},
onClawHubInstall,
}),
),
container,
);
await Promise.resolve();
expect(showModal).toHaveBeenCalledTimes(1 );
text = normalizeText(container);
expect(text).toContain("rate limited" );
expect(text).toContain("Installed github" );
expect(text).toContain("By OpenClaw (@openclaw)" );
expect(text).toContain("Latest: v1.2.3" );
expect(text).toContain("Platforms: macos, linux" );
expect(text).toContain("Added search support" );
container
.querySelector<HTMLButtonElement>(".md-preview-dialog__body .btn.primary" )
?.dispatchEvent(new MouseEvent("click" , { bubbles: true }));
expect(onClawHubInstall).toHaveBeenCalledTimes(1 );
expect(onClawHubInstall).toHaveBeenCalledWith("github" );
});
});
function installDialogMethod(
name: "showModal" | "close" ,
value: (this : HTMLDialogElement) => void ,
) {
const proto = HTMLDialogElement.prototype as HTMLDialogElement & Record<string, unknown>;
const original = Object.getOwnPropertyDescriptor(proto, name);
Object.defineProperty(proto, name, {
configurable: true ,
writable: true ,
value,
});
dialogRestores.push(() => {
if (original) {
Object.defineProperty(proto, name, original);
return ;
}
delete proto[name];
});
}
Messung V0.5 in Prozent C=99 H=99 G=98
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland