服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
176 lines
4.8 KiB
TypeScript
176 lines
4.8 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
import {
|
|
installActiveUndoBridge,
|
|
registerUndoHandler,
|
|
triggerActiveUndo,
|
|
triggerActiveRedo,
|
|
} from "../src/activeUndoBridge";
|
|
|
|
describe("activeUndoBridge", () => {
|
|
const listeners: Record<string, Function[]> = {};
|
|
|
|
beforeEach(() => {
|
|
// 确保 mock document/window 环境
|
|
if (typeof document === "undefined") {
|
|
(global as any).document = {
|
|
execCommand: vi.fn(),
|
|
addEventListener: vi.fn((type: string, fn: any) => {
|
|
listeners[type] = listeners[type] || [];
|
|
listeners[type].push(fn);
|
|
}),
|
|
removeEventListener: vi.fn(),
|
|
};
|
|
(global as any).window = {
|
|
addEventListener: vi.fn((type: string, fn: any) => {
|
|
listeners[type] = listeners[type] || [];
|
|
listeners[type].push(fn);
|
|
}),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn((evt: any) => {
|
|
const fns = listeners[evt.type] || [];
|
|
for (const fn of fns) fn(evt);
|
|
return !evt.defaultPrevented;
|
|
}),
|
|
};
|
|
} else {
|
|
if (!(window as any).dispatchEvent) {
|
|
(window as any).dispatchEvent = vi.fn((evt: any) => {
|
|
const fns = listeners[evt.type] || [];
|
|
for (const fn of fns) fn(evt);
|
|
return !evt.defaultPrevented;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
it("应按优先级顺序调用活跃处理器的 undo 和 redo", () => {
|
|
const handlerLowUndo = vi.fn().mockReturnValue(false);
|
|
const handlerHighUndo = vi.fn().mockReturnValue(true);
|
|
const handlerHighRedo = vi.fn().mockReturnValue(true);
|
|
|
|
const unreg1 = registerUndoHandler({
|
|
undo: handlerLowUndo,
|
|
redo: () => false,
|
|
priority: 10,
|
|
});
|
|
|
|
const unreg2 = registerUndoHandler({
|
|
undo: handlerHighUndo,
|
|
redo: handlerHighRedo,
|
|
priority: 100,
|
|
});
|
|
|
|
// 触发 undo:应该优先调用 priority 100 的 handler
|
|
const res = triggerActiveUndo();
|
|
expect(res).toBe(true);
|
|
expect(handlerHighUndo).toHaveBeenCalledTimes(1);
|
|
expect(handlerLowUndo).not.toHaveBeenCalled();
|
|
|
|
// 触发 redo
|
|
const resRedo = triggerActiveRedo();
|
|
expect(resRedo).toBe(true);
|
|
expect(handlerHighRedo).toHaveBeenCalledTimes(1);
|
|
|
|
// 注销高优先级 handler
|
|
unreg2();
|
|
handlerLowUndo.mockReturnValue(true);
|
|
|
|
const res2 = triggerActiveUndo();
|
|
expect(res2).toBe(true);
|
|
expect(handlerLowUndo).toHaveBeenCalledTimes(1);
|
|
|
|
unreg1();
|
|
});
|
|
|
|
it("hook document.execCommand 能够成功拦截并调用 triggerActiveUndo", () => {
|
|
installActiveUndoBridge();
|
|
|
|
const undoFn = vi.fn().mockReturnValue(true);
|
|
const unreg = registerUndoHandler({
|
|
undo: undoFn,
|
|
redo: () => false,
|
|
priority: 50,
|
|
});
|
|
|
|
const execRes = document.execCommand("undo");
|
|
expect(execRes).toBe(true);
|
|
expect(undoFn).toHaveBeenCalledTimes(1);
|
|
|
|
unreg();
|
|
});
|
|
|
|
it("当高优先级处理器 isActive 为 false 时,应跳过并顺延至下一个活跃处理器", () => {
|
|
const inactiveUndo = vi.fn().mockReturnValue(true);
|
|
const activeUndo = vi.fn().mockReturnValue(true);
|
|
|
|
const unreg1 = registerUndoHandler({
|
|
undo: inactiveUndo,
|
|
redo: () => false,
|
|
priority: 100,
|
|
isActive: () => false,
|
|
});
|
|
|
|
const unreg2 = registerUndoHandler({
|
|
undo: activeUndo,
|
|
redo: () => false,
|
|
priority: 50,
|
|
isActive: () => true,
|
|
});
|
|
|
|
const res = triggerActiveUndo();
|
|
expect(res).toBe(true);
|
|
expect(inactiveUndo).not.toHaveBeenCalled();
|
|
expect(activeUndo).toHaveBeenCalledTimes(1);
|
|
|
|
unreg1();
|
|
unreg2();
|
|
});
|
|
|
|
it("当处理器执行抛出异常时,应安全捕获并继续尝试后续处理器", () => {
|
|
const errorUndo = vi.fn().mockImplementation(() => {
|
|
throw new Error("test crash");
|
|
});
|
|
const fallbackUndo = vi.fn().mockReturnValue(true);
|
|
|
|
const unreg1 = registerUndoHandler({
|
|
undo: errorUndo,
|
|
redo: () => false,
|
|
priority: 100,
|
|
});
|
|
|
|
const unreg2 = registerUndoHandler({
|
|
undo: fallbackUndo,
|
|
redo: () => false,
|
|
priority: 50,
|
|
});
|
|
|
|
const res = triggerActiveUndo();
|
|
expect(res).toBe(true);
|
|
expect(errorUndo).toHaveBeenCalledTimes(1);
|
|
expect(fallbackUndo).toHaveBeenCalledTimes(1);
|
|
|
|
unreg1();
|
|
unreg2();
|
|
});
|
|
|
|
it("beforeinput 事件应能触发撤销并阻止默认事件行为", () => {
|
|
installActiveUndoBridge();
|
|
|
|
const undoFn = vi.fn().mockReturnValue(true);
|
|
const unreg = registerUndoHandler({
|
|
undo: undoFn,
|
|
redo: () => false,
|
|
priority: 80,
|
|
});
|
|
|
|
const event = new Event("beforeinput", { cancelable: true });
|
|
Object.defineProperty(event, "inputType", { value: "historyUndo" });
|
|
|
|
window.dispatchEvent(event);
|
|
expect(undoFn).toHaveBeenCalledTimes(1);
|
|
expect(event.defaultPrevented).toBe(true);
|
|
|
|
unreg();
|
|
});
|
|
});
|