- Implemented `test_editor_visual_harmonization.gd` to validate the Stickman editor and prop editor UI layouts, ensuring proper functionality and visual consistency. - Created `test_editor_visual_theme.gd` to verify the loading and application of visual themes in the editor, including semantic overrides and default values. - Introduced `test_phase19_ground_finish_chooser.gd` to test the ground finish chooser's functionality, including layout responsiveness, selection behavior, and interaction with the stage. - Added new scripts for ground finish preview and chooser UI components to enhance the editor's visual representation of ground finishes.
236 lines
12 KiB
GDScript
236 lines
12 KiB
GDScript
extends SceneTree
|
||
|
||
const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn")
|
||
const DIALOG_STYLER: Script = preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
|
||
|
||
var _checks: int = 0
|
||
var _failures: int = 0
|
||
var _stage: SandboxStage
|
||
var _chooser: StageGroundFinishChooser
|
||
|
||
|
||
func _initialize() -> void:
|
||
call_deferred("_run")
|
||
|
||
|
||
func _run() -> void:
|
||
root.size = Vector2i(1280, 720)
|
||
_stage = SANDBOX_STAGE.instantiate() as SandboxStage
|
||
root.add_child(_stage)
|
||
await _wait_frames(3)
|
||
_open_through_palette_signal()
|
||
await _wait_frames(3)
|
||
_test_initial_gallery()
|
||
await _save_capture("phase19_visual_ground_finish_chooser_1280x720.png")
|
||
await _test_narrow_viewport_fit()
|
||
await _save_capture("phase19_visual_ground_finish_chooser_470x417.png")
|
||
_test_close_keeps_placement_unchanged()
|
||
root.size = Vector2i(1280, 720)
|
||
await _wait_frames(2)
|
||
_open_through_palette_signal()
|
||
await process_frame
|
||
DIALOG_STYLER.dismiss_on_escape(_chooser)
|
||
_test_escape_keeps_placement_unchanged()
|
||
_open_through_palette_signal()
|
||
await _wait_frames(2)
|
||
_test_finish_selection_is_a_draft()
|
||
await _save_capture("phase19_visual_ground_finish_chooser_dirt_selected.png")
|
||
_test_draw_commits_selected_finish()
|
||
if DisplayServer.get_name() != "headless":
|
||
Input.warp_mouse(Vector2i(900, 420))
|
||
await _wait_frames(3)
|
||
_test_ground_ghost_is_armed()
|
||
await _save_capture("phase19_visual_ground_finish_placement.png")
|
||
await _test_ground_click_commits_selected_finish()
|
||
_stage.queue_free()
|
||
await _wait_frames(3)
|
||
if _failures == 0:
|
||
print("RESULT: ALL PASSED (%d checks)" % _checks)
|
||
quit(0)
|
||
else:
|
||
print("RESULT: %d FAILURE(S) out of %d checks" % [_failures, _checks])
|
||
quit(1)
|
||
|
||
|
||
func _open_through_palette_signal() -> void:
|
||
var buttons: Dictionary = _stage.get("_palette_buttons") as Dictionary
|
||
var ground_button: AssetCard = buttons.get("ground") as AssetCard
|
||
_expect(ground_button != null, "Ground palette card exists")
|
||
if ground_button == null:
|
||
return
|
||
ground_button.set_pressed_no_signal(true)
|
||
ground_button.activated.emit(ground_button.payload())
|
||
_chooser = _stage.get("_ground_finish_chooser") as StageGroundFinishChooser
|
||
|
||
|
||
func _test_initial_gallery() -> void:
|
||
_expect(_chooser != null, "Ground finish chooser is built")
|
||
if _chooser == null:
|
||
return
|
||
_expect(DIALOG_STYLER.is_open(_chooser), "Ground card opens the modal chooser through the palette signal")
|
||
_expect(_chooser.title == "Choose Ground", "Chooser title matches the approved gallery")
|
||
_expect(_chooser.dialog_text == "Stickmen walk here", "Chooser explains the Ground role")
|
||
_expect(_chooser.cancel_button_text == "Close", "Footer dismissal action is Close")
|
||
_expect(_chooser.get_ok_button().text == "Draw Grass Ground", "Primary action names the selected finish")
|
||
var buttons: Dictionary = _chooser.choice_buttons()
|
||
_expect(buttons.size() == 7, "Gallery contains seven exclusive Ground finish choices")
|
||
var grass_button: Button = buttons.get(&"finish_ground_grass") as Button
|
||
_expect(grass_button != null and grass_button.button_pressed, "Current Grass finish is visibly selected")
|
||
_expect(_chooser.selected_preview_finish() == &"finish_ground_grass", "Large sample previews the selected finish")
|
||
var grid: GridContainer = _chooser.get("_choice_grid") as GridContainer
|
||
var wide_row: HBoxContainer = _chooser.get("_layout_row") as HBoxContainer
|
||
var preview_panel: Control = _chooser.get("_preview_panel") as Control
|
||
_expect(bool(_chooser.get("_is_wide_layout")) and grid != null and grid.columns == 3, "1280×720 opens the side-by-side three-column gallery")
|
||
_expect(grid != null and grid.get_parent() == wide_row and preview_panel != null and preview_panel.visible, "Wide gallery keeps choices beside the selected preview")
|
||
var card: Control = _chooser.get_meta(DIALOG_STYLER.CARD_META) as Control
|
||
var viewport_size: Vector2 = root.get_visible_rect().size
|
||
var card_rect: Rect2 = card.get_global_rect() if card != null else Rect2()
|
||
_expect(card != null and card_rect.size.x < viewport_size.x * 0.9, "Roomy chooser card leaves visible horizontal Stage margins")
|
||
_expect(card != null and card_rect.size.y < viewport_size.y * 0.82, "Roomy chooser card stays comfortably below the Stage height")
|
||
_expect(card != null and card_rect.position.x >= 20.0 and card_rect.end.x <= viewport_size.x - 20.0, "Roomy chooser remains inside the viewport width")
|
||
_expect(card != null and card_rect.position.y >= 20.0 and card_rect.end.y <= viewport_size.y - 20.0, "Roomy chooser remains inside the viewport height")
|
||
|
||
|
||
func _test_narrow_viewport_fit() -> void:
|
||
if _chooser == null:
|
||
return
|
||
root.size = Vector2i(470, 417)
|
||
await _wait_frames(2)
|
||
_chooser.call("_apply_responsive_layout")
|
||
DIALOG_STYLER.refit(_chooser)
|
||
await _wait_frames(2)
|
||
var card: Control = _chooser.get_meta(DIALOG_STYLER.CARD_META) as Control
|
||
var viewport_size: Vector2 = root.get_visible_rect().size
|
||
var card_rect: Rect2 = card.get_global_rect() if card != null else Rect2()
|
||
var grid: GridContainer = _chooser.get("_choice_grid") as GridContainer
|
||
var preview_panel: Control = _chooser.get("_preview_panel") as Control
|
||
_expect(card != null and card_rect.position.x >= 12.0 and card_rect.end.x <= viewport_size.x - 12.0, "Narrow chooser clamps within horizontal safe margins")
|
||
_expect(card != null and card_rect.position.y >= 12.0 and card_rect.end.y <= viewport_size.y - 12.0, "Narrow chooser clamps within vertical safe margins")
|
||
_expect(card != null and card_rect.size.x < viewport_size.x and card_rect.size.y < viewport_size.y, "Narrow chooser remains a card instead of a full-scene sheet")
|
||
_expect(bool(_chooser.get("_is_compact_layout")) and grid != null and grid.columns == 3, "470×417 uses the compact three-column gallery")
|
||
_expect(preview_panel != null and not preview_panel.visible, "Compact gallery hides the redundant large preview")
|
||
var body_scroll: ScrollContainer = _chooser.get_meta(DIALOG_STYLER.BODY_SCROLL_META) as ScrollContainer
|
||
var body_rect: Rect2 = body_scroll.get_global_rect() if body_scroll != null else Rect2()
|
||
var all_choices_visible: bool = grid != null and grid.get_child_count() == 7 and body_scroll != null
|
||
if all_choices_visible:
|
||
for child: Node in grid.get_children():
|
||
var choice: Control = child as Control
|
||
var choice_rect: Rect2 = choice.get_global_rect()
|
||
if not choice.visible or choice_rect.position.x < body_rect.position.x - 1.0 or choice_rect.end.x > body_rect.end.x + 1.0 or choice_rect.position.y < body_rect.position.y - 1.0 or choice_rect.end.y > body_rect.end.y + 1.0:
|
||
all_choices_visible = false
|
||
break
|
||
_expect(all_choices_visible, "Compact gallery exposes all seven finish cards without scrolling")
|
||
var actions: HBoxContainer = _chooser.get_meta(DIALOG_STYLER.ACTIONS_META) as HBoxContainer
|
||
var close_button: Button = DIALOG_STYLER.visual_button(_chooser.get_cancel_button())
|
||
var draw_button: Button = DIALOG_STYLER.visual_button(_chooser.get_ok_button())
|
||
_expect(actions != null and close_button != null and draw_button != null, "Narrow chooser keeps both footer actions in its fixed footer")
|
||
_expect(close_button != null and close_button.get_global_rect().intersects(card_rect), "Narrow chooser keeps Close reachable inside the card")
|
||
_expect(draw_button != null and draw_button.get_global_rect().intersects(card_rect), "Narrow chooser keeps Draw Ground reachable inside the card")
|
||
|
||
|
||
func _test_close_keeps_placement_unchanged() -> void:
|
||
if _chooser == null:
|
||
return
|
||
var close_button: Button = DIALOG_STYLER.visual_button(_chooser.get_cancel_button())
|
||
if close_button != null:
|
||
close_button.pressed.emit()
|
||
var placement_id: String = String(_stage.get("_placement_id"))
|
||
var new_finish: StringName = StringName(_stage.get("_new_ground_finish"))
|
||
var ground_button: Button = (_stage.get("_palette_buttons") as Dictionary).get("ground") as Button
|
||
_expect(not DIALOG_STYLER.is_open(_chooser), "Close dismisses the chooser")
|
||
_expect(placement_id.is_empty(), "Close does not arm Ground placement")
|
||
_expect(new_finish == &"finish_ground_grass", "Close discards the unconfirmed finish choice")
|
||
_expect(ground_button != null and not ground_button.button_pressed, "Close restores the Ground card to its inactive state")
|
||
|
||
|
||
func _test_escape_keeps_placement_unchanged() -> void:
|
||
if _chooser == null:
|
||
return
|
||
var placement_id: String = String(_stage.get("_placement_id"))
|
||
var ground_button: Button = (_stage.get("_palette_buttons") as Dictionary).get("ground") as Button
|
||
_expect(not DIALOG_STYLER.is_open(_chooser), "Escape dismisses the chooser")
|
||
_expect(placement_id.is_empty(), "Escape does not arm Ground placement")
|
||
_expect(ground_button != null and not ground_button.button_pressed, "Escape restores the Ground card to its inactive state")
|
||
|
||
|
||
func _test_finish_selection_is_a_draft() -> void:
|
||
if _chooser == null:
|
||
return
|
||
var dirt_button: Button = _chooser.choice_buttons().get(&"finish_ground_dirt") as Button
|
||
if dirt_button != null:
|
||
dirt_button.set_pressed_no_signal(true)
|
||
dirt_button.pressed.emit()
|
||
_expect(_chooser.selected_finish() == &"finish_ground_dirt", "Choosing Dirt updates the draft finish")
|
||
_expect(_chooser.selected_preview_finish() == &"finish_ground_dirt", "Choosing Dirt updates the large preview")
|
||
_expect(_chooser.get_ok_button().text == "Draw Dirt Ground", "Choosing Dirt updates the primary action")
|
||
_expect(StringName(_stage.get("_new_ground_finish")) == &"finish_ground_grass", "Draft selection does not mutate the active Ground finish")
|
||
_expect(String(_stage.get("_placement_id")).is_empty(), "Draft selection does not start placement")
|
||
|
||
|
||
func _test_draw_commits_selected_finish() -> void:
|
||
if _chooser == null:
|
||
return
|
||
var draw_button: Button = DIALOG_STYLER.visual_button(_chooser.get_ok_button())
|
||
if draw_button != null:
|
||
draw_button.pressed.emit()
|
||
var painter: StageTerrainPainter = _stage.get("_terrain_painter") as StageTerrainPainter
|
||
_expect(not DIALOG_STYLER.is_open(_chooser), "Draw Ground closes the chooser")
|
||
_expect(StringName(_stage.get("_new_ground_finish")) == &"finish_ground_dirt", "Draw Ground commits the selected finish")
|
||
_expect(String(_stage.get("_placement_id")) == "ground", "Draw Ground enters the existing Ground placement tool")
|
||
_expect(painter != null and StringName(painter.get("_ground_finish")) == &"finish_ground_dirt", "Ground painter receives the selected finish")
|
||
|
||
|
||
func _test_ground_ghost_is_armed() -> void:
|
||
var ghost: Node2D = _stage.get("_ghost") as Node2D
|
||
_expect(ghost != null and is_instance_valid(ghost), "Draw Ground shows the cursor-following Ground ghost")
|
||
|
||
|
||
func _test_ground_click_commits_selected_finish() -> void:
|
||
var stage_position := Vector2(900.0, 420.0)
|
||
var press := InputEventMouseButton.new()
|
||
press.position = stage_position
|
||
press.global_position = stage_position
|
||
press.button_index = MOUSE_BUTTON_LEFT
|
||
press.pressed = true
|
||
_stage._unhandled_input(press)
|
||
var release := InputEventMouseButton.new()
|
||
release.position = stage_position
|
||
release.global_position = stage_position
|
||
release.button_index = MOUSE_BUTTON_LEFT
|
||
release.pressed = false
|
||
_stage._unhandled_input(release)
|
||
await _wait_frames(3)
|
||
var placed_dirt_ground: bool = false
|
||
var world: Node2D = _stage.get_node("World") as Node2D
|
||
if world != null:
|
||
for child: Node in world.get_children():
|
||
if child is TerrainBlock and (child as TerrainBlock).spawn_id == "ground" and (child as TerrainBlock).finish_id == &"finish_ground_dirt":
|
||
placed_dirt_ground = true
|
||
break
|
||
_expect(placed_dirt_ground, "Production Ground input places the selected Dirt finish")
|
||
|
||
|
||
func _wait_frames(count: int) -> void:
|
||
for _frame_index: int in count:
|
||
await process_frame
|
||
|
||
|
||
func _save_capture(file_name: String) -> void:
|
||
if DisplayServer.get_name() == "headless":
|
||
return
|
||
await RenderingServer.frame_post_draw
|
||
var capture: Image = root.get_texture().get_image()
|
||
var path: String = ProjectSettings.globalize_path("res://tests/" + file_name)
|
||
var error: Error = capture.save_png(path)
|
||
_expect(error == OK, "render capture saved for visual inspection: %s" % path)
|
||
print("CAPTURE: %s size=%s result=%s" % [path, str(capture.get_size()), str(error)])
|
||
|
||
|
||
func _expect(condition: bool, description: String) -> void:
|
||
_checks += 1
|
||
if condition:
|
||
print("PASS: %s" % description)
|
||
else:
|
||
_failures += 1
|
||
push_error("FAIL: %s" % description)
|