Files
stickman/tests/test_phase19_ground_finish_chooser.gd
T
ryan ddb298f631 Add visual harmonization tests for editor UI components and ground finish chooser
- 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.
2026-10-23 15:20:12 -04:00

236 lines
12 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.
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)