/** @vitest-environment jsdom */ import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { PageComponentProps } from "../contracts/page"; import { LlmSection } from "./ServerDetailPage"; const invokeAI = vi.hoisted(() => vi.fn()); vi.mock("../api/client", () => ({ platformApiClient: { invokeAI } })); (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const serverId = "server-scum-1785923898033"; const operations = { operations: [], begin: vi.fn(() => "operation-1"), succeed: vi.fn() } as unknown as PageComponentProps["operations"]; const onOpenTerminal = vi.fn(); let root: Root | null = null; let container: HTMLDivElement | null = null; afterEach(async () => { if (root) await act(async () => root?.unmount()); container?.remove(); root = null; container = null; vi.clearAllMocks(); }); async function submitPrompt() { container = document.createElement("div"); document.body.append(container); root = createRoot(container); await act(async () => root?.render()); const textarea = container.querySelector("textarea")!; await act(async () => { Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")!.set!.call(textarea, "Adjust max players"); textarea.dispatchEvent(new Event("input", { bubbles: true })); }); await act(async () => container!.querySelector("form")!.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }))); } describe("AI config assistance", () => { it("reports an outdated Platform response without claiming a config write", async () => { invokeAI.mockResolvedValue({ status: "ok", recommendation: "Mock AI recommendation for config.suggest: review the proposed change before dispatch.", configRecommendation: { key: "server.properties", suggestedConfig: "max-players=40\n", diffId: "old-review", diffSummary: "review required before config write dispatch" }, usage: { mocked: true } }); await submitPrompt(); expect(container!.textContent).toContain("平台未派发配置写入任务;请更新或重启 Platform 后端后重试。"); expect(container!.textContent).not.toContain("仅供参考"); expect(operations.begin).not.toHaveBeenCalled(); expect(onOpenTerminal).not.toHaveBeenCalled(); }); it("distinguishes an empty AI result from an outdated Platform response", async () => { invokeAI.mockResolvedValue({ status: "ok", recommendation: "No config produced", usage: { mocked: false } }); await submitPrompt(); expect(container!.textContent).toContain("AI 未生成可写入的配置内容,请调整请求后重试。"); expect(container!.textContent).not.toContain("请更新或重启 Platform 后端"); expect(operations.begin).not.toHaveBeenCalled(); }); it("opens the dispatched job and labels a mock provider", async () => { const job = { id: "job-config-1", serverInstanceId: serverId, capability: "config.write", targetKey: "server.properties", state: "queued" }; invokeAI.mockResolvedValue({ status: "ok", recommendation: "Mock recommendation", configRecommendation: { key: "server.properties", suggestedConfig: "max-players=40\n", diffSummary: "Job queued" }, configExecution: { status: "queued", job }, usage: { mocked: true } }); await submitPrompt(); expect(operations.succeed).toHaveBeenCalledWith("operation-1", expect.stringContaining(job.id), job); expect(onOpenTerminal).toHaveBeenCalledWith(job.id); expect(container!.textContent).toContain("模拟 AI Provider"); expect(container!.textContent).toContain("AI 配置写入已派发"); }); });