Fix SCUM file workbench interactions

This commit is contained in:
npc0-hue
2026-08-04 12:05:27 +08:00
parent 8a25f52d68
commit 86a50ec8fa
6 changed files with 158 additions and 49 deletions
+31 -7
View File
@@ -9,6 +9,19 @@ import { configurationCatalog, validateConfigPatch, validateStatePatch, validate
import { scumMigrationParityFixtures } from "./fixtures/scum-migration-parity.js";
const pageSource = readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), "../examples/scum-server-plugin/features/page.ts"), "utf8");
const declaredWorkspace = {
defaultDirectoryKey: "scum-config",
directories: [
{ key: "scum-config", label: "服务器配置", scope: "config" },
{ key: "scum-logs", label: "日志文件", scope: "logs" }
],
files: [
{ key: "scum-server-settings", directoryKey: "scum-config", label: "ServerSettings.ini", kind: "config", editable: true },
{ key: "scum-game-config", directoryKey: "scum-config", label: "Game.ini", kind: "config" },
{ key: "scum-admin-log", directoryKey: "scum-logs", label: "Admin.log", kind: "log", streamKey: "scum.admin" }
],
configFields: configurationCatalog
};
describe("SCUM plugin feature module", () => {
it("owns runtime allowlists without a version gate", () => {
@@ -56,17 +69,27 @@ describe("SCUM plugin feature module", () => {
expect(view.nodes).toContain("section:SCUM 文件管理");
expect(view.nodes).toContain("aside:SCUM 文件两级菜单");
expect(view.nodes).toContain("article:文件 ServerSettings.ini");
expect(view.texts.join("\n")).toContain("/SCUM/Saved/Config/WindowsServer");
expect(view.texts.join("\n")).toContain("/SCUM/Saved/SaveFiles/Logs");
expect(view.texts.join("\n")).toContain("scum-config · 配置声明");
expect(view.texts).toContain("刷新文件列表");
expect(view.texts).toContain("ServerSettings.ini");
expect(view.texts).toContain("Game.ini");
expect(view.texts).toContain("配置表单");
expect(view.texts).toContain("键值视图");
expect(view.texts).toContain("原文模式");
expect(view.buttons.find((button) => button.label === "刷新文件列表")?.disabled).toBe(false);
expect(view.buttons.find((button) => button.label === "读取文件")?.disabled).toBe(false);
expect(view.buttons.find((button) => button.label === "刷新结果")?.disabled).toBe(false);
for (const legacyText of ["玩家档案", "礼物", "受控状态", "载具", "地图轨迹", "查询玩家"]) expect(view.texts.join("\n")).not.toContain(legacyText);
});
it("does not invent a hardcoded SCUM file list when the platform workspace is missing", () => {
const view = renderAndCollect({ workspace: {} });
expect(view.texts.join("\n")).toContain("当前插件没有可展示的声明文件。");
expect(view.texts).not.toContain("ServerSettings.ini");
expect(view.texts).not.toContain("Game.ini");
expect(pageSource).not.toContain("fallbackFiles");
});
it("keeps declared log files in read-only raw view with encoding controls", () => {
const view = renderAndCollect({ directoryKey: "scum-logs", fileKey: "scum-admin-log" });
expect(view.nodes).toContain("article:文件 Admin.log");
@@ -90,15 +113,15 @@ describe("SCUM plugin feature module", () => {
expect(view.buttons.find((button) => button.label === "提交写入")?.disabled).toBe(true);
});
it("loads snapshots on selection or manual refresh without interval polling", () => {
it("loads snapshots on selection or manual refresh and bounded post-request refresh", () => {
expect(pageSource).toContain("getFileSnapshot(selectedFile.key)");
expect(pageSource).toContain("刷新结果");
expect(pageSource).toContain("loadFileSnapshot(selectedFile.key, true)");
expect(pageSource).not.toContain("setInterval");
expect(pageSource).not.toContain("setTimeout");
});
});
function renderAndCollect(options: { snapshot?: Record<string, unknown>; permissions?: string[]; directoryKey?: string; fileKey?: string } = {}) {
function renderAndCollect(options: { snapshot?: Record<string, unknown>; permissions?: string[]; directoryKey?: string; fileKey?: string; workspace?: Record<string, unknown> } = {}) {
const nodes: string[] = [];
const texts: string[] = [];
const buttons: Array<{ label: string; disabled: boolean }> = [];
@@ -116,15 +139,16 @@ function renderAndCollect(options: { snapshot?: Record<string, unknown>; permiss
stateCall += 1;
if (stateCall === 1 && options.directoryKey) return [options.directoryKey as T, () => undefined];
if (stateCall === 2 && options.fileKey) return [options.fileKey as T, () => undefined];
if (stateCall === 7 && options.snapshot) return [options.snapshot as T, () => undefined];
if (stateCall === 8 && options.snapshot) return [options.snapshot as T, () => undefined];
return [typeof initial === "function" ? (initial as () => T)() : initial, () => undefined];
}
};
renderPluginPage(react, {
context: { serverInstanceId: "server-1", permissions: options.permissions ?? ["server.files.read", "server.files.write", "server.logs.read"] },
availability: { available: true, features: [{ key: "config.manage", available: true }] },
workspace: {},
workspace: options.workspace ?? declaredWorkspace,
workspaceActions: {
refreshWorkspace: async () => declaredWorkspace,
requestFile: async (fileKey: string) => ({ status: "queued", message: fileKey }),
getFileSnapshot: async (fileKey: string) => ({ serverInstanceId: "server-1", pluginId: "game.scum", key: fileKey, state: "not-read" }),
writeFile: async (fileKey: string) => ({ status: "queued", message: fileKey })