DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,135 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
assignBinding,
|
||||
bindingFromEvent,
|
||||
commandForBinding,
|
||||
dispatchShortcut,
|
||||
effectiveBinding,
|
||||
formatBinding,
|
||||
isValidShortcutBinding,
|
||||
reservedBindingReason,
|
||||
} from "../src/shortcuts";
|
||||
|
||||
function keyEvent(code: string, options: Partial<KeyboardEvent> = {}): KeyboardEvent {
|
||||
return {
|
||||
code,
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
defaultPrevented: false,
|
||||
isComposing: false,
|
||||
repeat: false,
|
||||
preventDefault: vi.fn(),
|
||||
...options,
|
||||
} as KeyboardEvent;
|
||||
}
|
||||
|
||||
describe("应用快捷键", () => {
|
||||
it("将不同平台的主修饰键规范为 Mod 并使用物理键码", () => {
|
||||
expect(bindingFromEvent(keyEvent("KeyS", { metaKey: true, shiftKey: true }), true)).toBe("Mod+Shift+KeyS");
|
||||
expect(bindingFromEvent(keyEvent("KeyS", { ctrlKey: true, shiftKey: true }), false)).toBe("Mod+Shift+KeyS");
|
||||
expect(bindingFromEvent(keyEvent("Backslash", { ctrlKey: true }), false)).toBe("Mod+Backslash");
|
||||
expect(formatBinding("Mod+Backslash", true)).toMatch(/^⌘ /);
|
||||
expect(formatBinding("Mod+KeyO", false)).toBe("Ctrl+O");
|
||||
});
|
||||
|
||||
it("提供新增应用命令的默认键位", () => {
|
||||
const defaults = {
|
||||
"search.advanced": "Mod+Shift+KeyF",
|
||||
"settings.open": "Mod+Comma",
|
||||
"editor.closeOthers": "Mod+Shift+KeyW",
|
||||
"editor.closeAll": "Mod+Alt+KeyW",
|
||||
} as const;
|
||||
for (const [command, binding] of Object.entries(defaults)) {
|
||||
expect(effectiveBinding(command as keyof typeof defaults, {})).toBe(binding);
|
||||
expect(commandForBinding(binding, {})).toBe(command);
|
||||
}
|
||||
expect(effectiveBinding("version.open", {})).toBe("");
|
||||
expect(bindingFromEvent(keyEvent("Comma", { metaKey: true }), true)).toBe("Mod+Comma");
|
||||
});
|
||||
|
||||
it("拒绝裸字母、仅修饰键和系统或编辑器保留键", () => {
|
||||
expect(bindingFromEvent(keyEvent("KeyA"), false)).toBeNull();
|
||||
expect(bindingFromEvent(keyEvent("KeyA", { shiftKey: true }), false)).toBeNull();
|
||||
expect(bindingFromEvent(keyEvent("ControlLeft", { ctrlKey: true }), false)).toBeNull();
|
||||
expect(isValidShortcutBinding("Alt+Tab")).toBe(false);
|
||||
expect(isValidShortcutBinding("F5")).toBe(true);
|
||||
expect(isValidShortcutBinding("Shift+F5")).toBe(true);
|
||||
expect(reservedBindingReason("Mod+KeyQ")).toMatch(/系统/);
|
||||
expect(reservedBindingReason("Alt+ArrowLeft")).toMatch(/编辑器/);
|
||||
expect(reservedBindingReason("Mod+Shift+KeyG")).toMatch(/编辑器/);
|
||||
});
|
||||
|
||||
it("确认替换时解绑旧命令,清除和恢复默认有确定语义", () => {
|
||||
const assigned = assignBinding("settings.open", "Mod+KeyO", {});
|
||||
expect(assigned.displaced).toBe("archive.open");
|
||||
expect(assigned.overrides["archive.open"]).toBe("");
|
||||
expect(commandForBinding("Mod+KeyO", assigned.overrides)).toBe("settings.open");
|
||||
const cleared = assignBinding("settings.open", "", assigned.overrides);
|
||||
expect(effectiveBinding("settings.open", cleared.overrides)).toBe("");
|
||||
const restored = assignBinding("archive.open", "Mod+KeyO", cleared.overrides);
|
||||
expect(restored.overrides["archive.open"]).toBeUndefined();
|
||||
expect(effectiveBinding("archive.open", restored.overrides)).toBe("Mod+KeyO");
|
||||
});
|
||||
|
||||
it("弹窗、已处理的编辑器事件和不可用命令优先于应用分发", () => {
|
||||
const execute = vi.fn();
|
||||
const event = keyEvent("KeyO", { ctrlKey: true });
|
||||
expect(dispatchShortcut(event, {}, true, execute, undefined, undefined, false)).toBeNull();
|
||||
expect(dispatchShortcut(keyEvent("KeyO", { ctrlKey: true, defaultPrevented: true }), {}, false, execute, undefined, undefined, false)).toBeNull();
|
||||
expect(dispatchShortcut(event, {}, false, execute, undefined, () => false, false)).toBeNull();
|
||||
expect(execute).not.toHaveBeenCalled();
|
||||
expect(dispatchShortcut(event, {}, false, execute, undefined, () => true, false)).toBe("archive.open");
|
||||
expect(event.preventDefault).toHaveBeenCalledOnce();
|
||||
expect(execute).toHaveBeenCalledWith("archive.open");
|
||||
});
|
||||
|
||||
it("关闭其他和关闭全部标签的组合键分发到不同命令", () => {
|
||||
const execute = vi.fn();
|
||||
expect(dispatchShortcut(keyEvent("KeyW", { ctrlKey: true, shiftKey: true }), {}, false, execute, undefined, undefined, false)).toBe("editor.closeOthers");
|
||||
expect(dispatchShortcut(keyEvent("KeyW", { ctrlKey: true, altKey: true }), {}, false, execute, undefined, undefined, false)).toBe("editor.closeAll");
|
||||
expect(execute.mock.calls.map(([command]) => command)).toEqual(["editor.closeOthers", "editor.closeAll"]);
|
||||
});
|
||||
|
||||
it("workspace.save 快捷键异常拦截:saveActiveTab 抛出错误时捕获并路由至消息提示", async () => {
|
||||
const errorMessages: string[] = [];
|
||||
const mockMessageApi = {
|
||||
error: (msg: string) => errorMessages.push(msg),
|
||||
};
|
||||
|
||||
// 模拟 saveActiveTab 抛出 GUI 未应用修改错误
|
||||
const saveActiveTab = vi.fn().mockRejectedValue(
|
||||
new Error("当前文件存在未应用的界面修改,请先在界面中点击【应用修改】后再保存"),
|
||||
);
|
||||
|
||||
function executeSaveShortcut() {
|
||||
saveActiveTab().catch((err: any) => {
|
||||
mockMessageApi.error(err?.message ?? String(err));
|
||||
});
|
||||
}
|
||||
|
||||
const event = keyEvent("KeyS", { ctrlKey: true });
|
||||
const dispatched = dispatchShortcut(
|
||||
event,
|
||||
{},
|
||||
false,
|
||||
(cmd) => {
|
||||
if (cmd === "workspace.save") executeSaveShortcut();
|
||||
},
|
||||
undefined,
|
||||
() => true,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(dispatched).toBe("workspace.save");
|
||||
expect(saveActiveTab).toHaveBeenCalledOnce();
|
||||
|
||||
// 等待微任务异步捕获完成
|
||||
await Promise.resolve();
|
||||
|
||||
expect(errorMessages).toEqual([
|
||||
"当前文件存在未应用的界面修改,请先在界面中点击【应用修改】后再保存",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user