8e6b3abac3
- PluginManager: 采用上游 useMarketPlugins(300行: 分页append/loadingMore/autoLoadBlocked/竞态AbortController/highlightFilter) + MarketPluginList(无限滚动LoadMoreSentinel+PluginViewToggle卡片/列表切换); OfficialPluginList.module.less 保留三处 var(--color-border) 定制+追加上游移动端@media; index.module.less 三向并合; 本地 index.tsx 保留(operator 权限 gate) - Market: MarketPanel(新 install: MarketInstallController 接口)/useMarketInstall/useMarketSearch/SkillIcon/ResultCard 采用上游 - Channels: ChannelDrawer/channelConfig/constants 上游版; 定制点保留(channelIcons console=var(--color-primary)、PendingApprovalsDrawer var(--color-success)) - api/types/chat.ts 采用上游(ChatGroup/ChatSource/ChatGroupKind/group_id); pages/Agent/Skills/index.tsx 适配 useMarketInstall - utils: chatGroups.ts + 14 个上游缺失工具测试补齐; CronJobs 测试补齐 - 测试修复: design-mock 补 Tabs 桩(上游 PluginViewToggle 依赖); MarketPluginList.test 采用上游版; skill.test 品牌映射云超服; lazyWithRetry.test 适配本地 dev-server 回退 - 验证: tsc 0 错误、build 通过、阶段 4 相关 45 测试文件 427 用例全过
189 lines
5.2 KiB
TypeScript
189 lines
5.2 KiB
TypeScript
/**
|
|
* Skill source / automation / install-origin helpers used across the
|
|
* skill pool UI. Pure presentation logic; defects here mislabel skill
|
|
* state (e.g. show "synced" for a stale builtin).
|
|
*/
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
getSkillDisplaySource,
|
|
isSkillBuiltin,
|
|
getPoolSkillAutomationState,
|
|
getPoolBuiltinStatusLabel,
|
|
getPoolBuiltinStatusTone,
|
|
INSTALLED_FROM_LABELS,
|
|
deriveInstalledFromLabel,
|
|
} from "./skill";
|
|
|
|
describe("getSkillDisplaySource", () => {
|
|
it("maps builtin to builtin", () => {
|
|
expect(getSkillDisplaySource("builtin")).toBe("builtin");
|
|
});
|
|
|
|
it("maps any other source to customized", () => {
|
|
expect(getSkillDisplaySource("custom")).toBe("customized");
|
|
expect(getSkillDisplaySource("")).toBe("customized");
|
|
});
|
|
});
|
|
|
|
describe("isSkillBuiltin", () => {
|
|
it("recognizes the plain builtin source", () => {
|
|
expect(isSkillBuiltin("builtin")).toBe(true);
|
|
});
|
|
|
|
it("recognizes prefixed builtin sources", () => {
|
|
expect(isSkillBuiltin("builtin:browser")).toBe(true);
|
|
});
|
|
|
|
it("recognizes the system source as builtin", () => {
|
|
expect(isSkillBuiltin("system")).toBe(true);
|
|
});
|
|
|
|
it("treats custom sources as non-builtin", () => {
|
|
expect(isSkillBuiltin("custom")).toBe(false);
|
|
});
|
|
|
|
it("treats undefined as non-builtin", () => {
|
|
expect(isSkillBuiltin(undefined)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getPoolSkillAutomationState", () => {
|
|
it("customized skill with sync off is off", () => {
|
|
expect(
|
|
getPoolSkillAutomationState({
|
|
source: "custom",
|
|
auto_sync: false,
|
|
auto_update: true,
|
|
}),
|
|
).toBe("off");
|
|
});
|
|
|
|
it("customized skill ignores auto_update and follows auto_sync", () => {
|
|
expect(
|
|
getPoolSkillAutomationState({
|
|
source: "custom",
|
|
auto_sync: true,
|
|
auto_update: false,
|
|
}),
|
|
).toBe("on");
|
|
});
|
|
|
|
it("builtin with both flags aligned on is on", () => {
|
|
expect(
|
|
getPoolSkillAutomationState({
|
|
source: "builtin",
|
|
auto_sync: true,
|
|
auto_update: true,
|
|
}),
|
|
).toBe("on");
|
|
});
|
|
|
|
it("builtin with both flags aligned off is off", () => {
|
|
expect(
|
|
getPoolSkillAutomationState({
|
|
source: "builtin",
|
|
auto_sync: false,
|
|
auto_update: false,
|
|
}),
|
|
).toBe("off");
|
|
});
|
|
|
|
it("builtin with divergent flags is mixed", () => {
|
|
expect(
|
|
getPoolSkillAutomationState({
|
|
source: "builtin",
|
|
auto_sync: true,
|
|
auto_update: false,
|
|
}),
|
|
).toBe("mixed");
|
|
expect(
|
|
getPoolSkillAutomationState({
|
|
source: "builtin",
|
|
auto_sync: false,
|
|
auto_update: true,
|
|
}),
|
|
).toBe("mixed");
|
|
});
|
|
|
|
it("treats missing flags as false", () => {
|
|
expect(getPoolSkillAutomationState({ source: "builtin" })).toBe("off");
|
|
});
|
|
});
|
|
|
|
describe("getPoolBuiltinStatusLabel", () => {
|
|
// t stub returns the key so assertions read the translation key used
|
|
const t = ((key: string) => key) as any;
|
|
|
|
it("maps each known sync status to its translation key", () => {
|
|
expect(getPoolBuiltinStatusLabel("synced", t)).toBe(
|
|
"skillPool.statusUpToDate",
|
|
);
|
|
expect(getPoolBuiltinStatusLabel("outdated", t)).toBe(
|
|
"skillPool.statusOutdated",
|
|
);
|
|
expect(getPoolBuiltinStatusLabel("not_synced", t)).toBe(
|
|
"skillPool.statusNotSynced",
|
|
);
|
|
expect(getPoolBuiltinStatusLabel("conflict", t)).toBe(
|
|
"skillPool.statusConflict",
|
|
);
|
|
});
|
|
|
|
it("renders a dash for empty or unknown status", () => {
|
|
expect(getPoolBuiltinStatusLabel("", t)).toBe("-");
|
|
expect(getPoolBuiltinStatusLabel(undefined, t)).toBe("-");
|
|
expect(getPoolBuiltinStatusLabel("-", t)).toBe("-");
|
|
});
|
|
});
|
|
|
|
describe("getPoolBuiltinStatusTone", () => {
|
|
it("maps outdated to the outdated tone", () => {
|
|
expect(getPoolBuiltinStatusTone("outdated")).toBe("outdated");
|
|
});
|
|
|
|
it("maps synced to the synced tone", () => {
|
|
expect(getPoolBuiltinStatusTone("synced")).toBe("synced");
|
|
});
|
|
|
|
it("uses neutral for every other status", () => {
|
|
expect(getPoolBuiltinStatusTone("not_synced")).toBe("neutral");
|
|
expect(getPoolBuiltinStatusTone("conflict")).toBe("neutral");
|
|
expect(getPoolBuiltinStatusTone("")).toBe("neutral");
|
|
expect(getPoolBuiltinStatusTone(undefined)).toBe("neutral");
|
|
});
|
|
});
|
|
|
|
describe("deriveInstalledFromLabel", () => {
|
|
it("maps known install origins to display labels", () => {
|
|
expect(deriveInstalledFromLabel("qwenpaw")).toBe("云超服");
|
|
expect(deriveInstalledFromLabel("skills-sh")).toBe("skills.sh");
|
|
expect(deriveInstalledFromLabel("github")).toBe("GitHub");
|
|
});
|
|
|
|
it("passes through unknown origins verbatim", () => {
|
|
expect(deriveInstalledFromLabel("some-new-hub")).toBe("some-new-hub");
|
|
});
|
|
|
|
it("renders an empty string for missing origin", () => {
|
|
expect(deriveInstalledFromLabel(undefined)).toBe("");
|
|
expect(deriveInstalledFromLabel("")).toBe("");
|
|
});
|
|
|
|
it("covers every documented origin key", () => {
|
|
expect(Object.keys(INSTALLED_FROM_LABELS).sort()).toEqual(
|
|
[
|
|
"aliyun",
|
|
"clawhub",
|
|
"github",
|
|
"lobehub",
|
|
"modelscope",
|
|
"qwenpaw",
|
|
"skills-sh",
|
|
"skillsmp",
|
|
"url",
|
|
"zip",
|
|
].sort(),
|
|
);
|
|
});
|
|
});
|