/** @vitest-environment jsdom */ import { act, useState } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { ServerFileWorkspaceResponse, ServerInstanceResponse } from "../api/types"; import type { OperationTracker } from "../stores/operations"; import { ServerConfigEditor } from "./ServerConfigEditor"; const apiMocks = vi.hoisted(() => ({ getServerFileReadSnapshot: vi.fn(), getServerFileWorkspace: vi.fn(), readServerFile: vi.fn() })); vi.mock("../api/client", () => ({ platformApiClient: apiMocks })); const instance = { id: "server-1", name: "SCUM Alpha" } as ServerInstanceResponse; const workspace = { serverInstanceId: "server-1", pluginId: "game.scum", defaultDirectoryKey: "scum-config", directories: [{ key: "scum-config", label: "服务器配置", scope: "config" }], files: [{ key: "scum-server-settings", directoryKey: "scum-config", label: "ServerSettings.ini", kind: "config", editable: true }], configFields: [], transfer: { channel: "run-file-transfer", uploadChunkSizeBytes: 1, downloadChunkSizeBytes: 1, maxInlineEditBytes: 64, maxBrowserUploadBytes: 64, notes: [] }, declaredOnly: true } as ServerFileWorkspaceResponse; let root: Root | null = null; let container: HTMLDivElement | null = null; (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; beforeEach(() => { vi.useFakeTimers(); apiMocks.getServerFileWorkspace.mockResolvedValue(workspace); let snapshotCalls = 0; apiMocks.getServerFileReadSnapshot.mockImplementation(async () => { snapshotCalls += 1; return snapshotCalls === 1 ? { serverInstanceId: "server-1", pluginId: "game.scum", key: "scum-server-settings", state: "not-read" } : { serverInstanceId: "server-1", pluginId: "game.scum", key: "scum-server-settings", state: "ready", content: "[General]\nscum.MaxPlayers=63\n", version: 1, checksum: "sha256:test" }; }); apiMocks.readServerFile.mockResolvedValue({ status: "queued", serverInstanceId: "server-1", pluginId: "game.scum", operation: "read", key: "scum-server-settings", job: { id: "job-read", state: "queued" } }); }); afterEach(async () => { if (root) await act(async () => root?.unmount()); container?.remove(); root = null; container = null; vi.clearAllMocks(); vi.useRealTimers(); }); describe("ServerConfigEditor", () => { it("does not restart the same snapshot request when operation state rerenders the parent", async () => { container = document.createElement("div"); document.body.append(container); root = createRoot(container); function Wrapper() { const [, rerender] = useState(0); const operations: OperationTracker = { operations: [], begin: () => { rerender((value) => value + 1); return "operation-read"; }, update: () => undefined, succeed: () => undefined, fail: () => undefined, isPending: () => false }; return undefined} />; } await act(async () => root?.render()); await act(async () => { await Promise.resolve(); }); await act(async () => { await vi.advanceTimersByTimeAsync(500); }); await act(async () => { await Promise.resolve(); }); expect(apiMocks.readServerFile).toHaveBeenCalledTimes(1); expect(apiMocks.getServerFileReadSnapshot).toHaveBeenCalledTimes(2); }); });