# tests/unit/presentation/battle_narrator_test.gd — 战斗叙述直接单测 # 断言面 = 纯函数文案:休整/商店/终局三类叙述把数值带进去(玩家读数面由这里出)、 # 胜负两态文案互异、未知输入有兜底不吐空串。期望值由注入内容数出来。 # 运行:godot --headless --path game --script res://tests/unit/presentation/battle_narrator_test.gd extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const NarratorScript := preload("res://presentation/battle_narrator.gd") # 叙述寻址的 runner 替身:只提供 boss_name 实际读的两项(当前节点、关卡行) class _BossRunner: var level_row: Dictionary = {"level_index": 1} var node: Dictionary = {} func current_node() -> Dictionary: return node func _init() -> void: _test_numeric_lines_carry_values() _test_end_text_differs_by_result() _test_phase_text_nonempty() _test_boss_name_uses_boss_rowset() quit(Kit.finish("battle_narrator_test")) func _test_numeric_lines_carry_values() -> void: var rest: String = NarratorScript.rest_line(12, 30, 40) Kit.check("休整叙述带入回复量与当前血量(实际「%s」)" % rest, rest.contains("12") and rest.contains("30") and rest.contains("40")) var shop: String = NarratorScript.shop_line(77) Kit.check("商店叙述带入 GP 数(实际「%s」)" % shop, shop.contains("77")) Kit.check("奖励行与休整行互异(不是同一句套所有场景)", NarratorScript.reward_line() != rest) func _test_end_text_differs_by_result() -> void: # 关卡收束(level_end=true)恒为「关卡通关!」——单场胜负文案只在非收束态出现 var level: String = NarratorScript.battle_end_text("lose", true) Kit.check("关卡收束态恒为「关卡通关!」(实际「%s」)" % level, level == "关卡通关!") var win: String = NarratorScript.battle_end_text("victory", false) var lose: String = NarratorScript.battle_end_text("lose", false) Kit.check("单场胜负文案互异(胜「%s」/负「%s」)" % [win, lose], win != lose) Kit.check("胜利文案非空(实际「%s」)" % win, win != "") var unknown: String = NarratorScript.battle_end_text("something_else", false) Kit.check("未知结果有兜底文案不吐空串(实际「%s」)" % unknown, unknown != "") func _test_phase_text_nonempty() -> void: var t: String = NarratorScript.phase_text(1, "", false, false, "") Kit.check("相位叙述非空(实际「%s」)" % t, t != "") var boss: String = NarratorScript.phase_text(2, "", false, true, "灰矮人石君王") Kit.check("Boss 在场时叙述带入 Boss 名(实际「%s」)" % boss, boss.contains("灰矮人石君王")) # 叙述寻址按行类取 Boss 行集(S 清单 S15 换代):Boss 节点名必须来自 (关卡, boss, 带) 行集, # 不能落到同深度的战斗行集——期望名由表行集首行读出,不写死字面量。 func _test_boss_name_uses_boss_rowset() -> void: var db = load("res://core/config_db.gd").new() var loaded: bool = db.load_tables("res://data/tables", false) Kit.check_quiet(loaded, "叙述寻址用例:数值表加载成功") if not loaded: return var boss_row := {} for row in db.enemy.rows: if int(row.level_index) == 1 and String(row.get("row_kind", "")) == "boss": boss_row = row break Kit.check_quiet(not boss_row.is_empty(), "叙述寻址用例:第一关有 Boss 行") if boss_row.is_empty(): return var boss_depth: int = int(boss_row.depth_band) var boss_rows: Array = db.resolve_enemy_rowset(1, boss_depth, "boss") var battle_rows: Array = db.resolve_enemy_rowset(1, boss_depth, "battle") var boss_expect := String(boss_rows[0].get("display_name", "")) var battle_name := String(battle_rows[0].get("display_name", "")) var runner := _BossRunner.new() runner.node = {"node_depth": boss_depth, "type": "boss"} var got: String = NarratorScript.boss_name(runner, db) Kit.check("Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" % [boss_expect, got], got == boss_expect) Kit.check("Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" % [boss_expect, battle_name], boss_expect != battle_name)