Files
90CNServer/pvf/frontend/tests/independent-drop-gui.test.ts
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

59 lines
3.7 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { applyDropPatches, convertDropRecord, dropListText, dropRecordText, editDropFields, replaceDropSpan, sourceOffset } from "../src/gui/independentDrop";
import type { IndependentDropRow, SkillNumber } from "../bindings/pvfine/services/models";
function fixture() {
const line="0 20 3176 1000000 20 30 40 50 1 2 3 4 5 0 99 -1 1";
const normalized="# 😀\n[independent drop]\n"+line+"\n[list]\n3176 20 3030 80\n[/list]\n[replace list]\n[dungeon condition]\n1 20 2\n[/replace list]\n[/independent drop]\n";
const start=normalized.indexOf(line);
const fields=[...line.matchAll(/-?\d+/g)].map(m=>({value:Number(m[0]),start:start+m.index!,end:start+m.index!+m[0].length,tokenType:0}) as SkillNumber);
return {text:normalized.replaceAll("\n","\r\n"),row:{fields,start,end:normalized.indexOf("[/independent drop]"),hasExtensions:true} as IndependentDropRow};
}
describe("独立掉落源位置编辑",()=>{
it("概率只修改目标字段,保留扩展条件、注释、emoji 和 CRLF",()=>{
const {text,row}=fixture();
expect(editDropFields(text,row,[{column:3,value:752500},{column:8,value:7}])).toBe(text.replace("1000000","752500").replace("1 2 3 4 5 0 99","7 2 3 4 5 0 99"));
});
it("处理列表增删、零权重和重复 ID",()=>{
const content=dropListText([{id:3176,weight:0},{id:3176,weight:2}]);
expect(content).toContain("3176\t0");
const {text}=fixture();const normalized=text.replaceAll("\r\n","\n");
const from=normalized.indexOf("[list]")+6,to=normalized.indexOf("[/list]");
const edited=replaceDropSpan(text,from,to,"\n"+content+"\n");
expect(edited).toContain("[replace list]\r\n[dungeon condition]");
expect(edited).toContain("3176\t2");
expect(sourceOffset(text,3)).toBe(3);
});
it("三种模式生成完整记录",()=>{
const {row}=fixture(),values=row.fields!.map(f=>f.value);
for(const mode of [0,1,2]){
const record=dropRecordText([...values.slice(0,16),mode],[{id:3176,weight:1}],17);
expect(record.includes("[list]")).toBe(mode!==0);
if(mode===2)expect(record).toContain("\t\t17\n");
}
});
it("转换掉落模式时保留相邻注释和额外 section",()=>{
const {text,row}=fixture();
const clean=text.replace(/\[replace list\][\s\S]*?\[\/replace list\]\r\n/,"");
const normalized=clean.replaceAll("\r\n","\n");
const source={...row,hasExtensions:false,listTagStart:normalized.indexOf("[list]"),listStart:normalized.indexOf("[list]")+6,listEnd:normalized.indexOf("[/list]")};
const values=row.fields!.map(f=>f.value);values[16]=2;
const edited=convertDropRecord(clean,source,values,[],17);
expect(edited).toContain("[list]\n\t\t17\n\t[/list]");
expect(edited).toContain("# 😀\r\n[independent drop]\r\n");
expect(edited.match(/\[list\]/g)).toHaveLength(1);
expect(edited).toContain("[/independent drop]\r\n");
expect(()=>convertDropRecord(text,row,values,[],17)).toThrow("扩展");
});
it("拒绝过期、重叠、未知模式、溢出和非法概率",()=>{
const {text,row}=fixture();
expect(()=>editDropFields(text.replace("1000000","900000"),row,[{column:3,value:1}])).toThrow("草稿已变化");
for(const value of [-1,1000001,NaN,1.5])expect(()=>editDropFields(text,row,[{column:3,value}])).toThrow();
expect(()=>editDropFields(text,row,[{column:16,value:0}])).toThrow();
expect(()=>editDropFields(text,row,[{column:3,value:1},{column:3,value:2}])).toThrow("重叠");
expect(()=>applyDropPatches(text,[{token:row.fields![1]!,value:2147483648}])).toThrow();
expect(()=>dropListText([{id:1,weight:-1}])).toThrow();
expect(()=>sourceOffset(text,99999)).toThrow();
expect(()=>dropRecordText([...row.fields!.slice(0,16).map(f=>f.value),5],[],0)).toThrow("未知");
});
});