Files
gplan-2/game/tests/unit/presentation/modal_flow_test.gd
T
DKChan 5f9d8e7b77 chore(代码卫生): 文件头零工作日志红线落文+全仓头注变更叙事清偿(2026-10-15 用户令)
- CLAUDE.md 硬性红线新增「文件头零工作日志」;code-conventions.md 新增 §1.4.1(含反面案例)
- 全仓 .gd 文件头注清偿:删除全部带日期条目、批次叙事段与迁移退役叙述,保留并改写为
  现在时的职责、接口契约、GDD 锚内容(main.gd 头注 42→31 行、save_manager.gd 49→45 行)
- 纯注释改动零行为变更,按用户令免测试免冒烟
2026-10-15 04:08:38 +08:00

114 lines
6.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 模态编排纯逻辑类单测(ModalFlow 直接 new() 进 run_all;
# 互斥域谓词/三例外输入锁/演播后排窗挂起/演播完边沿派发/遮挡记账)。
# 载体零场景零树——纯 RefCounted + 哑 Control。
# 运行:godot --headless --path game --script res://tests/unit/presentation/modal_flow_test.gd(expect 0)
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
const ModalFlowScript := preload("res://presentation/modal_flow.gd") # 探针载体禁全局类名裸引用惯例同源
const ModalStackScript := preload("res://presentation/modal_stack.gd")
func _init() -> void:
# 哑层与桩 Callable(纯逻辑类零场景前提的证明面)
var start := Control.new()
var inter := Control.new()
var settlement_holder := {"s": null} # lambda 对局部按值捕获——容器装载
var rows := {"empty": true}
var calls := {"flush": 0, "open_sync": 0, "close_sync": 0, "replay": 0}
var flow = ModalFlowScript.new()
Kit.check_quiet(flow != null and flow.stack != null, "V6 ModalFlow 直接 new()(stack 内建)〔迭代27 T4〕")
flow.start_visible_get = func() -> bool: return start.visible
flow.inter_visible_get = func() -> bool: return inter.visible
flow.settlement_get = func() -> Variant: return settlement_holder["s"]
flow.rows_empty_get = func() -> bool: return rows["empty"]
flow.flush_rows = func() -> void: calls["flush"] += 1
flow.on_open_sync = func() -> void: calls["open_sync"] += 1
flow.on_close_sync = func() -> void: calls["close_sync"] += 1
flow.replay_click = func() -> void: calls["replay"] += 1
# V6-1 互斥域登记+遮挡记账
var a := Control.new() # 普通层
var dbg := Control.new() # 调试浮窗(non_locking)
var map := Control.new() # 关卡地图屏(screen_state)
flow.register(a)
flow.register(dbg, [ModalStackScript.TAG_NON_LOCKING])
flow.register(map, [ModalStackScript.TAG_SCREEN_STATE])
var tag_dump := "tags a=%s dbg=%s map=%s const=%s" % [str(flow.stack._tags.get(a, "MISSING")), str(flow.stack._tags.get(dbg, "MISSING")), str(flow.stack._tags.get(map, "MISSING")), ModalStackScript.TAG_NON_LOCKING]
print("[DBG] ", tag_dump)
flow.open(a)
Kit.check_quiet(a.visible and calls["open_sync"] == 1 and calls["flush"] == 1, "V6 开侧编排(flush→open→sync 单口序)〔迭代27 T4〕")
# 遮挡记账(登记序=z 序:开低层隐其上方可见层、关闭恢复——低层开启期高层照会不可见的呈现保真)
flow.open(map)
Kit.check_quiet(map.visible, "V6 开最高层(上方无可隐)〔迭代27 T4〕")
flow.open(a)
Kit.check_quiet(a.visible and not map.visible, "V6 遮挡记账(开低层隐其上方可见层)〔迭代27 T4〕")
flow.close(a)
Kit.check_quiet(map.visible and calls["close_sync"] == 1, "V6 关闭恢复(关低层显其上方被隐层+关侧同步)〔迭代27 T4〕")
# V6-2 输入锁谓词:互斥域随层登记(non_locking 不计)+ 三例外
start.visible = false
inter.visible = false
settlement_holder["s"] = Object.new()
flow.open(dbg)
Kit.check_quiet(not flow.locks_input(), "V6 输入锁:调试浮窗 non_locking 不计(T2 防线)〔迭代27 T4〕")
flow.close(dbg)
flow.open(a)
Kit.check_quiet(flow.locks_input(), "V6 输入锁:普通层开态计锁〔迭代27 T4〕")
flow.close(a)
flow.close(map) # 登记册全关(close(a) 经遮挡恢复 map——须一并关账才是「全关」态)
settlement_holder["s"] = null
Kit.check_quiet(flow.locks_input(), "V6 输入锁例外三:战斗缺位即锁〔迭代27 T4〕")
settlement_holder["s"] = Object.new()
Kit.check_quiet(not flow.locks_input(), "V6 输入锁:战斗在座+登记册全关=不锁〔迭代27 T4〕")
start.visible = true
Kit.check_quiet(flow.locks_input(), "V6 输入锁例外一:开始层可见即锁〔迭代27 T4〕")
start.visible = false
inter.visible = true
Kit.check_quiet(flow.locks_input(), "V6 输入锁例外二:过场层可见即锁〔迭代27 T4〕")
inter.visible = false
Kit.check_quiet(not flow.locks_input(), "V6 输入锁:全例外解除+登记册全关=不锁〔迭代27 T4〕")
# V6-3 角标谓词(状态/调试两屏同款守卫——与旧排除式逐位同值)
flow.open(dbg)
Kit.check_quiet(not flow.badge_greyed_battle() and not flow.badge_greyed_map(),
"V6 角标谓词:浮窗开态两形制均不置灰〔迭代27 T4〕")
flow.close(dbg)
flow.open(map)
Kit.check_quiet(not flow.badge_greyed_map() and flow.badge_greyed_battle(),
"V6 角标谓词:地图屏 screen_state 地图形制不计/战斗形制计(旧排除式 [debug] 与 [level,debug] 同值面)〔迭代27 T4〕")
flow.close(map)
flow.open(a)
Kit.check_quiet(flow.badge_greyed_battle() and flow.badge_greyed_map(),
"V6 角标谓词:普通层开态两形制均置灰〔迭代27 T4〕")
flow.close(a)
# V6-4 演播后排窗挂起+边沿派发(T17 语义)
var end_ov := Control.new()
end_ov.visible = false
rows["empty"] = false
var sync_before1: int = calls["open_sync"]
flow.open_after_playback(end_ov)
Kit.check_quiet(flow._deferred == end_ov and not end_ov.visible and calls["open_sync"] == sync_before1,
"V6 演播后排窗:在播挂起含视觉(不照会上屏——零开侧同步)〔迭代27 T4〕")
rows["empty"] = true
var sync_before2: int = calls["open_sync"]
flow.pump_ended()
Kit.check_quiet(flow._deferred == null and end_ov.visible and calls["open_sync"] == sync_before2 + 1,
"V6 演播完边沿:挂起 modal 先开(队列空判定由 rows_empty_get 承载)〔迭代27 T4〕")
flow.close(end_ov)
flow.pump_ended()
Kit.check_quiet(calls["replay"] == 1, "V6 演播完边沿:无挂起→锁期点击重放派发〔迭代27 T4〕")
# 挂起兜底(他路径开新层先结挂起防永不兑现)
rows["empty"] = false
flow.open_after_playback(end_ov)
rows["empty"] = true
var other := Control.new()
var sync_before: int = calls["open_sync"]
flow.open(other)
Kit.check_quiet(end_ov.visible and flow._deferred == null and calls["open_sync"] == sync_before + 2,
"V6 挂起兜底:开新层先结挂起(T17 防永不兑现——挂起层先开+新层后开两拍)〔迭代27 T4〕")
quit(Kit.finish("modal_flow"))