mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-11-05 01:01:29 +08:00
146 lines
5.2 KiB
TypeScript
146 lines
5.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import * as React from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import type { ProfileConfig } from "@ccr/core/contracts/app.ts";
|
|
import { DeleteProfileDialog, ProfileView } from "@ccr/ui/pages/home/components/profiles.tsx";
|
|
import { AppI18nContext, appCopy } from "@ccr/ui/pages/home/shared/i18n.tsx";
|
|
import { createProfileDraft, normalizeUnknownProfileItem, profileDraftWithDetectedAppPath } from "@ccr/ui/pages/home/shared/profiles.ts";
|
|
import { appConfigFixture } from "../fixtures/index.ts";
|
|
|
|
const profile: ProfileConfig = {
|
|
agent: "claude-code",
|
|
enabled: true,
|
|
id: "claude-code-main",
|
|
model: "openai/gpt-5.2",
|
|
name: "Claude Code Main"
|
|
};
|
|
|
|
test("DeleteProfileDialog identifies the profile and requires an explicit confirmation", () => {
|
|
const html = renderToStaticMarkup(
|
|
<DeleteProfileDialog onClose={() => undefined} onConfirm={() => undefined} profile={profile} />
|
|
);
|
|
|
|
assert.match(html, /Delete Profile/);
|
|
assert.match(html, /Delete this agent profile from the configuration\?/);
|
|
assert.match(html, /Claude Code Main/);
|
|
assert.match(html, /Claude Code/);
|
|
assert.match(html, />Cancel<\/button>/);
|
|
assert.match(html, />Delete<\/button>/);
|
|
});
|
|
|
|
test("DeleteProfileDialog renders the Chinese confirmation copy", () => {
|
|
const html = renderToStaticMarkup(
|
|
<AppI18nContext.Provider value={appCopy.zh}>
|
|
<DeleteProfileDialog onClose={() => undefined} onConfirm={() => undefined} profile={profile} />
|
|
</AppI18nContext.Provider>
|
|
);
|
|
|
|
assert.match(html, /删除 Agent 配置/);
|
|
assert.match(html, /从配置中删除这个 Agent 配置档案?/);
|
|
assert.match(html, />取消<\/button>/);
|
|
assert.match(html, />删除<\/button>/);
|
|
});
|
|
|
|
test("ProfileView keeps launch actions directly accessible in an aligned action bar", () => {
|
|
const config = appConfigFixture();
|
|
config.profile.profiles = [
|
|
{
|
|
...profile,
|
|
scope: "ccr",
|
|
surface: "auto"
|
|
},
|
|
{
|
|
agent: "zcode",
|
|
enabled: true,
|
|
id: "zcode-main",
|
|
model: "openai/gpt-5.2",
|
|
name: "ZCode Main",
|
|
scope: "global",
|
|
surface: "app"
|
|
}
|
|
];
|
|
|
|
const html = renderToStaticMarkup(
|
|
<ProfileView
|
|
addProfile={() => undefined}
|
|
applyError=""
|
|
config={config}
|
|
copyProfileCliCommand={() => undefined}
|
|
editProfile={() => undefined}
|
|
openProfileApp={() => undefined}
|
|
profileRuntimeStatus={{ profiles: [] }}
|
|
removeProfile={() => undefined}
|
|
stopProfileApp={() => undefined}
|
|
updateProfileItem={() => undefined}
|
|
/>
|
|
);
|
|
|
|
assert.equal(html.match(/aria-label="(?:Claude Code Main|ZCode Main) Profile actions"/g)?.length, 2);
|
|
assert.match(html, /aria-label="Copy CLI command Claude Code Main"/);
|
|
assert.match(html, /aria-label="Start App Claude Code Main"/);
|
|
assert.match(html, /aria-label="Start App ZCode Main"/);
|
|
assert.doesNotMatch(html, /aria-label="Copy CLI command ZCode Main"/);
|
|
});
|
|
|
|
test("detected CHATGPT_APP_PATH is used as the Codex profile default", () => {
|
|
const detectedPath = "/Applications/ChatGPT.app/Contents/MacOS/ChatGPT";
|
|
const draft = profileDraftWithDetectedAppPath(createProfileDraft("codex"), ` ${detectedPath} `);
|
|
|
|
assert.equal(draft.appPath, detectedPath);
|
|
assert.equal(profileDraftWithDetectedAppPath({ ...draft, appPath: "/custom/chatgpt" }, detectedPath).appPath, "/custom/chatgpt");
|
|
assert.equal(profileDraftWithDetectedAppPath(createProfileDraft("claude-code"), detectedPath).appPath, "");
|
|
});
|
|
|
|
test("detected OPENCODE_APP_PATH is used as the OpenCode profile default", () => {
|
|
const detectedPath = "/Applications/OpenCode.app/Contents/MacOS/OpenCode";
|
|
const draft = profileDraftWithDetectedAppPath(createProfileDraft("opencode"), undefined, detectedPath);
|
|
assert.equal(draft.appPath, detectedPath);
|
|
assert.equal(profileDraftWithDetectedAppPath({ ...draft, appPath: "/custom/opencode" }, undefined, detectedPath).appPath, "/custom/opencode");
|
|
});
|
|
|
|
test("Grok CLI profile defaults to a CCR-scoped CLI entry", () => {
|
|
const draft = createProfileDraft("grok");
|
|
|
|
assert.equal(draft.name, "Grok CLI");
|
|
assert.equal(draft.scope, "ccr");
|
|
assert.equal(draft.surface, "cli");
|
|
});
|
|
|
|
test("persisted Grok profiles are normalized to the supported launch scope", () => {
|
|
const profile = normalizeUnknownProfileItem({
|
|
agent: "grok-cli",
|
|
enabled: true,
|
|
id: "grok-work",
|
|
model: "Provider/model",
|
|
name: "Grok Work",
|
|
scope: "global",
|
|
surface: "app"
|
|
}, 0);
|
|
|
|
assert.equal(profile?.agent, "grok");
|
|
assert.equal(profile?.scope, "ccr");
|
|
assert.equal(profile?.surface, "cli");
|
|
});
|
|
|
|
test("OpenCode profiles support local CLI and App configuration", () => {
|
|
const draft = createProfileDraft("opencode");
|
|
assert.equal(draft.name, "OpenCode");
|
|
assert.equal(draft.configFile, "~/.config/opencode/opencode.jsonc");
|
|
assert.equal(draft.surface, "cli");
|
|
|
|
const profile = normalizeUnknownProfileItem({
|
|
agent: "open-code",
|
|
appPath: "/Applications/OpenCode.app",
|
|
enabled: true,
|
|
id: "opencode-work",
|
|
model: "Provider/model",
|
|
name: "OpenCode Work",
|
|
scope: "ccr",
|
|
surface: "auto"
|
|
}, 0);
|
|
assert.equal(profile?.agent, "opencode");
|
|
assert.equal(profile?.appPath, "/Applications/OpenCode.app");
|
|
assert.equal(profile?.surface, "auto");
|
|
});
|