Files
90CNServer/pvf/frontend/tests/active-undo-bridge.test.ts
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

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();
});
});