服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
136 lines
5.7 KiB
TypeScript
136 lines
5.7 KiB
TypeScript
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([
|
|
"当前文件存在未应用的界面修改,请先在界面中点击【应用修改】后再保存",
|
|
]);
|
|
});
|
|
});
|