mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
166 lines
5.0 KiB
TypeScript
166 lines
5.0 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { PiNativePromptResources } from "@/components/prompts/PiNativePromptResources";
|
|
import { promptsApi, type PiPromptFileKind } from "@/lib/api/prompts";
|
|
|
|
vi.mock("react-i18next", () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("sonner", () => ({
|
|
toast: {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
const renderResources = () => {
|
|
const client = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
return render(
|
|
<QueryClientProvider client={client}>
|
|
<PiNativePromptResources />
|
|
</QueryClientProvider>,
|
|
);
|
|
};
|
|
|
|
describe("PiNativePromptResources", () => {
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.spyOn(promptsApi, "getPiPromptFile").mockImplementation(
|
|
async (kind: PiPromptFileKind) => ({
|
|
kind,
|
|
path:
|
|
kind === "system_override"
|
|
? "/agent/SYSTEM.md"
|
|
: "/agent/APPEND_SYSTEM.md",
|
|
exists: kind === "system_append",
|
|
revision: kind === "system_append" ? "append-revision" : "missing",
|
|
content: kind === "system_append" ? "append" : "",
|
|
}),
|
|
);
|
|
vi.spyOn(promptsApi, "listPiPromptTemplates").mockResolvedValue([
|
|
{
|
|
slug: "empty",
|
|
content: "",
|
|
revision: "empty-revision",
|
|
},
|
|
]);
|
|
vi.spyOn(promptsApi, "upsertPiPromptTemplate").mockResolvedValue({
|
|
slug: "new-empty",
|
|
content: "",
|
|
revision: "created-revision",
|
|
});
|
|
vi.spyOn(promptsApi, "replacePiPromptFile").mockImplementation(
|
|
async (kind, _revision, content) => ({
|
|
kind,
|
|
path: "/agent/SYSTEM.md",
|
|
exists: true,
|
|
revision: "saved-empty",
|
|
content,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("uses file presence as state, rejects blank direct saves, and permits empty templates", async () => {
|
|
renderResources();
|
|
|
|
await waitFor(() => expect(screen.getByText("/empty")).toBeInTheDocument());
|
|
expect(screen.getByText("pi.prompts.active")).toBeInTheDocument();
|
|
expect(screen.getByText("pi.prompts.inactive")).toBeInTheDocument();
|
|
|
|
const instructionEditors = screen.getAllByPlaceholderText(
|
|
"pi.prompts.instructionPlaceholder",
|
|
);
|
|
fireEvent.change(instructionEditors[1], { target: { value: "" } });
|
|
const saveButtons = screen.getAllByRole("button", { name: "common.save" });
|
|
expect(saveButtons[1]).toBeDisabled();
|
|
fireEvent.change(instructionEditors[1], {
|
|
target: { value: "new append" },
|
|
});
|
|
expect(saveButtons[1]).toBeEnabled();
|
|
fireEvent.click(saveButtons[1]);
|
|
await waitFor(() =>
|
|
expect(promptsApi.replacePiPromptFile).toHaveBeenCalledWith(
|
|
"system_append",
|
|
"append-revision",
|
|
"new append",
|
|
),
|
|
);
|
|
|
|
fireEvent.change(screen.getByPlaceholderText("pi.prompts.templateSlug"), {
|
|
target: { value: "new-empty" },
|
|
});
|
|
const create = screen.getByRole("button", {
|
|
name: "pi.prompts.createTemplate",
|
|
});
|
|
expect(create).toBeEnabled();
|
|
fireEvent.click(create);
|
|
|
|
await waitFor(() =>
|
|
expect(promptsApi.upsertPiPromptTemplate).toHaveBeenCalledWith(
|
|
"new-empty",
|
|
"missing",
|
|
"",
|
|
),
|
|
);
|
|
});
|
|
|
|
it("does not offer prompt-template names that Pi cannot invoke portably", async () => {
|
|
renderResources();
|
|
|
|
const slug = screen.getByPlaceholderText("pi.prompts.templateSlug");
|
|
const create = screen.getByRole("button", {
|
|
name: "pi.prompts.createTemplate",
|
|
});
|
|
|
|
for (const invalid of ["release notes", "bad:name", "CON"]) {
|
|
fireEvent.change(slug, { target: { value: invalid } });
|
|
expect(create).toBeDisabled();
|
|
expect(
|
|
screen.getByText("pi.prompts.templateSlugInvalid"),
|
|
).toBeInTheDocument();
|
|
}
|
|
|
|
fireEvent.change(slug, { target: { value: "release.v2" } });
|
|
expect(create).toBeEnabled();
|
|
expect(
|
|
screen.queryByText("pi.prompts.templateSlugInvalid"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("requires confirmation before creating the dangerous SYSTEM override", async () => {
|
|
renderResources();
|
|
|
|
const instructionEditors = await screen.findAllByPlaceholderText(
|
|
"pi.prompts.instructionPlaceholder",
|
|
);
|
|
fireEvent.change(instructionEditors[0], {
|
|
target: { value: "replace the system prompt" },
|
|
});
|
|
fireEvent.click(screen.getAllByRole("button", { name: "common.save" })[0]);
|
|
|
|
expect(promptsApi.replacePiPromptFile).not.toHaveBeenCalled();
|
|
expect(
|
|
screen.getByText("pi.prompts.activateOverrideTitle"),
|
|
).toBeInTheDocument();
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "pi.prompts.activateOverride" }),
|
|
);
|
|
await waitFor(() =>
|
|
expect(promptsApi.replacePiPromptFile).toHaveBeenCalledWith(
|
|
"system_override",
|
|
"missing",
|
|
"replace the system prompt",
|
|
),
|
|
);
|
|
});
|
|
});
|