# ui_test —— P1 UI 工具层 headless 自检(解析器 + 构建器 + 窗口管理器)。 # godot --headless --path project --script ui_test.gd extends SceneTree const UiScript = preload("res://ui/uiscript.gd") const UiBuild = preload("res://ui/ui_build.gd") const UiManager = preload("res://ui/ui_manager.gd") const Dialogs = preload("res://ui/dialogs.gd") const FIXTURE := """ import uiScriptLocale ROOT = "d:/ymir work/ui/public/" PAD = 10 window = { "name" : "TestWin", "style" : ("float",), "x" : SCREEN_WIDTH/2 - 250, "y" : SCREEN_HEIGHT/2 - 40, "width" : 280, "height" : 105, "children" : ( { "name" : "board", "type" : "board_with_titlebar", "x" : 0, "y" : 0, "width" : 280, "height" : 105, "title" : uiScriptLocale.HELLO, "children" : ( { "name" : "message", "type" : "text", "x" : PAD, "y" : 38, "text" : uiScriptLocale.MESSAGE, "text_horizontal_align" : "center", "text_color" : 0xffF8BF24, }, { "name" : "accept", "type" : "button", "x" : 0, "y" : 63, "width" : 61, "height" : 21, "horizontal_align" : "center", "text" : uiScriptLocale.OK, "default_image" : ROOT + "middle_button_01.sub", }, ), }, ), } """ var _fail := 0 func _ck(c: bool, m: String) -> void: if not c: _fail += 1 printerr("FAIL: " + m) func _init() -> void: _test_parser() _test_builder() _test_manager() _test_dialogs() _test_real_file() if _fail == 0: print("PASS: ui_test (parser + builder + manager)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1) func _test_parser() -> void: var us := UiScript.new() us.screen = Vector2i(1920, 1080) us.locale = func(k: String) -> String: return {"HELLO": "Hi", "MESSAGE": "Body", "OK": "OK"}.get(k, "") var w := us.parse_text(FIXTURE) _ck(us.last_error == "", "parser: no error (%s)" % us.last_error) _ck(w.get("name") == "TestWin", "parser: window name") _ck(int(w.get("x")) == 710, "parser: arithmetic SCREEN_WIDTH/2-250 = 710, got %s" % w.get("x")) _ck(int(w.get("width")) == 280, "parser: width") var kids: Array = w.get("children", []) _ck(kids.size() == 1 and kids[0].get("type") == "board_with_titlebar", "parser: child type") _ck(kids[0].get("title") == "Hi", "parser: locale HELLO -> 'Hi'") var inner: Array = kids[0].get("children", []) _ck(inner.size() == 2, "parser: 2 grandchildren") _ck(inner[0].get("text") == "Body", "parser: locale MESSAGE") _ck(int(inner[0].get("text_color")) == 0xffF8BF24, "parser: hex color") _ck(int(inner[1].get("x")) == 0 and inner[1].get("horizontal_align") == "center", "parser: const PAD + align kept") # 空输入 / 坏输入 _ck(UiScript.new().parse_text("import x\n").is_empty(), "parser: no window -> {}") func _test_builder() -> void: var us := UiScript.new() us.screen = Vector2i(1920, 1080) us.locale = func(k: String) -> String: return k var spec := us.parse_text(FIXTURE) var r := UiBuild.build(spec, "") # 无 assets -> 主题兜底 var root: Control = r.root _ck(root is Control, "builder: root is Control") _ck(root.size == Vector2(280, 105), "builder: root size") _ck(root.position == Vector2(710, 500), "builder: root pos, got %s" % root.position) var nodes: Dictionary = r.nodes _ck(nodes.has("accept") and nodes["accept"] is Button, "builder: accept is Button") _ck(nodes["accept"].text == "OK", "builder: button text") # horizontal_align center:x = (280 - 61)/2 + 0 = 109(父 board 宽 280) _ck(nodes["accept"].position.x == 109, "builder: center align x = 109, got %s" % nodes["accept"].position.x) _ck(nodes.has("message") and nodes["message"] is Label, "builder: message is Label") _ck(nodes["message"].horizontal_alignment == HORIZONTAL_ALIGNMENT_CENTER, "builder: text align") _ck(nodes["board"] is Panel, "builder: board is Panel") _ck(nodes["board"].has_node("TitleText"), "builder: board_with_titlebar has title label") func _test_manager() -> void: var ui: CanvasLayer = UiManager.new() get_root().add_child(ui) ui._ready() # ensure built (add_child 后一帧才 _ready,这里直接调) var w1 := Control.new() w1.custom_minimum_size = Vector2(100, 100) var w2 := Control.new() ui.open(w1) ui.open(w2, true) # modal _ck(ui.top() == w2, "manager: w2 on top") _ck(ui._dim.visible, "manager: modal dim shown") _ck(ui.close_top(), "manager: close_top ok") _ck(ui.top() == w1, "manager: w1 now top") _ck(not ui._dim.visible, "manager: dim hidden after modal closed") _ck(not ui.close_top() == false, "manager: close last") ui.close_top() _ck(ui.top() == null, "manager: stack empty") ui.queue_free() func _test_dialogs() -> void: var ui: CanvasLayer = UiManager.new() get_root().add_child(ui) ui._ready() var dlg := Dialogs.new(ui) var got := [0] dlg.confirm("delete?", func(): got[0] = 1, func(): got[0] = -1) _ck(ui.top() != null, "dialogs: confirm opened") _ck(ui._dim.visible, "dialogs: confirm is modal") # 找到「确定」按钮点一下 var ok_btn: Button = null for n in ui.top().get_children(): if n is Button and n.text == "确定": ok_btn = n _ck(ok_btn != null, "dialogs: 确定 button present") if ok_btn: ok_btn.pressed.emit() _ck(got[0] == 1, "dialogs: on_ok fired") _ck(ui.top() == null, "dialogs: closed after confirm") ui.queue_free() func _test_real_file() -> void: var p := AssetRoot.sub("uiscript/uiscript/popupdialog.py") if not FileAccess.file_exists(p): print(" (skip real-file test: %s not present)" % p) return var us := UiScript.new() us.locale = func(k: String) -> String: return "L_" + k var w := us.parse_file(p) _ck(us.last_error == "", "real: popupdialog parsed (%s)" % us.last_error) _ck(w.get("name") == "PopupDialog", "real: name == PopupDialog, got %s" % w.get("name")) var kids: Array = w.get("children", []) _ck(kids.size() >= 1 and kids[0].get("type") == "board", "real: first child is board") var found_btn := false for c in kids[0].get("children", []): if c.get("type") == "button" and c.get("name") == "accept": found_btn = true _ck(String(c.get("default_image", "")).ends_with(".sub"), "real: button default_image is .sub") _ck(found_btn, "real: found accept button") # 构建也不炸 var r := UiBuild.build(w, "") _ck(r.root is Control and r.nodes.has("accept"), "real: build ok")