# test_refine_parity.gd —— 40250 强化系统 1:1 纯净无头自检套件 # 验证: # 1. RefineDialogNew 动态几何尺寸计算与 Tooltip # 2. 材料槽位与 ThinBoard,背包存量白字/红字对比 # 3. 金币费用与不足警示 # 4. 100% 成功率与 type==5 免弹窗直接发包 # 5. type==3 (铁匠石)、type==2 (祝福卷轴)、type==0 (普通铁匠) 对应专属 QuestionDialog2 文案 # 6. 二次确认弹窗 "否" 拦截发包、"是" 触发发包 # 7. 40250 取消规范:点击取消/ESC/关闭标题栏发送 refine(255, 255) # 8. 10.0m 超距自动取消发包 # 9. 强化成功与失败音画回显 extends SceneTree const RefineUI = preload("res://ui/refine_ui.gd") class FakeAudio extends Node: var calls: Array[String] = [] func play_ui(name: String) -> void: calls.append(name) class FakeClient extends Node: signal refine_ask(info: Dictionary) signal refine_result(ok: bool) var calls: Array = [] var gold := 1000000 var inventory: Array = [ {"vnum": 30001, "count": 5, "cell": 0, "window": 1}, {"vnum": 30002, "count": 1, "cell": 1, "window": 1}, ] var main_pos := Vector3(100.0, 10.0, 100.0) func is_in_game() -> bool: return true func get_gold() -> int: return gold func get_inventory() -> Array: return inventory func get_main_vid() -> int: return 1001 func get_entity(vid: int) -> Dictionary: if vid == 1001: return {"pos": main_pos} return {} func refine(pos: int, type: int) -> bool: calls.append(["refine", pos, type]) return true var _fail := 0 func _ck(c: bool, m: String) -> void: if not c: _fail += 1 printerr("FAIL: " + m) func _init() -> void: _run() if _fail == 0: print("PASS: test_refine_parity (40250 RefineDialogNew 1:1 parity)") quit(0) else: printerr("%d check(s) failed in test_refine_parity" % _fail) quit(1) func _run() -> void: var canvas := CanvasLayer.new() get_root().add_child(canvas) var fc := FakeClient.new() get_root().add_child(fc) var fa := FakeAudio.new() get_root().add_child(fa) var ru: Node = RefineUI.new() get_root().add_child(ru) ru.setup(fc, canvas, null, fa) _ck(not ru.is_open(), "1. 初始状态关闭") # --- Case 1: 普通强化 (type 0, prob 60 < 100, 材料足) --- fc.refine_ask.emit({ "type": 0, "pos": 5, "src_vnum": 10, "result_vnum": 11, "cost": 50000, "prob": 60, "materials": [{"vnum": 30001, "count": 2}], }) _ck(ru.is_open(), "2. 收到 refine_ask 打开窗口") _ck(ru._prob_label.text == "成功率: 60%", "3. 成功率文本准确") _ck(ru._cost_label.text == "费用: 50000 金", "4. 费用文本准确") _ck(ru._material_container.get_child_count() == 1, "5. 材质行生成") var mat_lbl: Label = ru._material_container.get_child(0).get_child(1).get_child(0) _ck(mat_lbl.modulate.r < 0.95 and mat_lbl.modulate.g > 0.8, "6. 材料足 (5>=2) 显示白字") # 点击精炼 -> 应当弹出 QuestionDialog2,而不是直接发包 ru._refine_button.pressed.emit() _ck(ru._question_dialog.visible, "7. prob 60 < 100 必须弹出 QuestionDialog2") _ck(ru._qd_message1.text.contains("销毁"), "8. type 0 对应彻底销毁文案") _ck(fc.calls.is_empty(), "9. 弹窗期间未发包") # 点击 "否" -> 取消确认,精炼窗口不关,不发包 ru._qd_cancel_btn.pressed.emit() _ck(not ru._question_dialog.visible, "10. 点否关闭确认弹窗") _ck(ru.is_open(), "11. 主强化窗依然保持开启") _ck(fc.calls.is_empty(), "12. 依然未发包") # 再次点击精炼,并点 "是" -> 确认发包 ru._refine_button.pressed.emit() ru._qd_accept_btn.pressed.emit() _ck(fc.calls == [["refine", 5, 0]], "13. 确认后发出 client.refine(pos=5, type=0)") _ck(not ru.is_open(), "14. 确认后主窗关闭") fc.calls.clear() # --- Case 2: 100% 成功率免弹窗 --- fc.refine_ask.emit({ "type": 0, "pos": 2, "src_vnum": 1, "result_vnum": 2, "cost": 1000, "prob": 100, "materials": [], }) ru._refine_button.pressed.emit() _ck(not ru._question_dialog.visible, "15. 100% 成功率不弹 QuestionDialog2") _ck(fc.calls == [["refine", 2, 0]], "16. 100% 成功率直接发出 refine(2, 0)") _ck(not ru.is_open(), "17. 发包后关闭") fc.calls.clear() # --- Case 3: 祝福卷轴 (type 2) 降级文案 --- fc.refine_ask.emit({ "type": 2, "pos": 10, "src_vnum": 15, "result_vnum": 16, "cost": 10000, "prob": 30, "materials": [], }) ru._refine_button.pressed.emit() _ck(ru._question_dialog.visible, "18. type 2 弹确认框") _ck(ru._qd_message1.text.contains("降低1级"), "19. type 2 对应降级文案") ru._qd_cancel_btn.pressed.emit() # 点击取消按钮 -> 40250 发送 refine(255, 255) ru._cancel_button.pressed.emit() _ck(not ru.is_open(), "20. 取消后关闭") _ck(fc.calls == [["refine", 255, 255]], "21. 取消发包 refine(255, 255)") fc.calls.clear() # --- Case 4: 铁匠石 (type 3) 加成文案 --- fc.refine_ask.emit({ "type": 3, "pos": 12, "src_vnum": 18, "result_vnum": 19, "cost": 20000, "prob": 50, "materials": [], }) ru._refine_button.pressed.emit() _ck(ru._qd_message1.text.contains("加成") or ru._qd_message1.text.contains("提升"), "22. type 3 对应概率提升文案") # ESC 取消 -> 发送 refine(255, 255) ru.cancel_refine() _ck(not ru.is_open() and fc.calls == [["refine", 255, 255]], "23. cancel_refine 发送 (255, 255)") fc.calls.clear() # --- Case 5: 材料不足红字标红 --- fc.refine_ask.emit({ "type": 0, "pos": 1, "src_vnum": 1, "result_vnum": 2, "cost": 1000, "prob": 80, "materials": [{"vnum": 30002, "count": 5}], # 背包只有 1 个 }) var shortage_lbl: Label = ru._material_container.get_child(0).get_child(1).get_child(0) _ck(shortage_lbl.modulate.r > 0.9 and shortage_lbl.modulate.g < 0.4, "24. 材料不足 (1<5) 显示红字") ru.close() # --- Case 6: 10 米超距自动发包取消 --- fc.refine_ask.emit({ "type": 0, "pos": 1, "src_vnum": 1, "result_vnum": 2, "cost": 1000, "prob": 80, "materials": [], }) _ck(ru.is_open(), "25. 再次打开") # 玩家移动 5 米 (仍在 10 米内) fc.main_pos = Vector3(105.0, 10.0, 100.0) ru._process(0.1) _ck(ru.is_open(), "26. 移动 5 米不关闭") # 玩家移动 15 米 (超出 10 米) fc.main_pos = Vector3(120.0, 10.0, 100.0) ru._process(0.1) _ck(not ru.is_open(), "27. 移动 15 米超出限制自动关闭") _ck(fc.calls == [["refine", 255, 255]], "28. 超距自动发送取消包 refine(255, 255)") fc.calls.clear() # --- Case 7: 结果音效与状态回显 --- fc.refine_result.emit(true) _ck(ru.is_open() and ru._prob_label.text.contains("成功"), "29. 成功结果回显") _ck(fa.calls.has("refine_success.wav"), "30. 播放 refine_success.wav") fc.refine_result.emit(false) _ck(ru._prob_label.text.contains("失败"), "31. 失败结果回显") _ck(fa.calls.has("refine_fail.wav"), "32. 播放 refine_fail.wav")