spaceship/tests/test_skill_menu.gd

197 lines
7.7 KiB
GDScript3
Raw Normal View History

extends SceneTree
# Headless test: run with
# godot --headless -s res://tests/test_skill_menu.gd
# Forgejo #93, both halves of it.
#
# A mouse could not drive the research menu at all. Input reaches the tree
# through BuildController._unhandled_input as raw events (a Control that
# eats them through the GUI system would swallow the drag - see
# SkillTreeView's header), and the tree only ever read touch events. That
# was invisible for as long as the project set emulate_touch_from_mouse,
# which handed it a synthetic touch per click; with that setting gone, the
# menu is inert under a mouse and there is no other way to research
# anything on a desktop.
#
# The panel also still had a hole in it. The buy button below the readout
# went away in #83, and nothing moved into the space, so ~180px of empty
# panel sat under the text while the tree canvas above it stayed cramped.
#
# Covers:
# A. a left click selects a node, through the real routing in
# BuildController rather than by calling the view directly
# B. a second click on the SAME node researches it, which is #83's
# confirm gesture reached with a mouse
# C. holding the left button and moving scrolls the canvas, and moving
# with no button held does NOT - a hovering pointer must not drag the
# tree out from under the reader
# D. the wheel scrolls, in both directions
# E. the readout sits at the bottom of the panel and the tree canvas fills
# everything above it, so neither the dead band nor the cramped tree
# comes back
const GAP_TOLERANCE: float = 48.0
var game_state: Node
var scene: Node
var controller: Node
var skill_menu: Control
var tree_view: Control
var stage: int = 0
var elapsed: float = 0.0
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.buildings.clear()
game_state.extractors.clear()
game_state.reset_progression()
game_state.reset_formation()
game_state.rocks = 500
scene = load("res://world/Main.tscn").instantiate()
root.add_child(scene)
controller = scene.get_node("BuildController")
skill_menu = scene.get_node("UI/SkillMenu")
tree_view = scene.get_node("UI/SkillMenu/Tree")
func _click(at: Vector2, pressed: bool) -> void:
var event: InputEventMouseButton = InputEventMouseButton.new()
event.button_index = MOUSE_BUTTON_LEFT
event.pressed = pressed
event.position = at
# Through the controller, not straight into the view: the missing piece
# was the ROUTING, so a test that called tree_view.handle_mouse_button()
# itself would have passed against the reported bug.
controller._unhandled_input(event)
func _wheel(button: int) -> void:
var event: InputEventMouseButton = InputEventMouseButton.new()
event.button_index = button
event.pressed = true
event.position = tree_view.global_position + tree_view.size * 0.5
controller._unhandled_input(event)
func _move(relative: Vector2) -> void:
var event: InputEventMouseMotion = InputEventMouseMotion.new()
event.relative = relative
event.position = tree_view.global_position + tree_view.size * 0.5
controller._unhandled_input(event)
func _node_at(id: String) -> Vector2:
return tree_view.global_position + tree_view.node_screen_pos(game_state.skill_node(id))
func _process(delta: float) -> bool:
elapsed += delta
if stage == 0:
scene.get_node("AsteroidField").set_process(false)
scene.get_node("EnemySpawner").set_process(false)
scene.get_node("BuildingField").place(GameState.BUILDING_MOTHERSHIP, Vector2(400.0, 0.0))
elapsed = 0.0
stage = 1
return false
if stage != 1 or elapsed < 0.2:
return false
stage = 2
var mothership: Node2D = null
for building in scene.get_node("BuildingField").spawned:
if is_instance_valid(building) and building.building_type == GameState.BUILDING_MOTHERSHIP:
mothership = building
controller.current_building = mothership
controller._on_action_pressed()
if not skill_menu.visible:
return _fail("the mothership did not open the research menu")
# --- A. a click selects --------------------------------------------
var shipyards: Vector2 = _node_at("shipyards")
_click(shipyards, true)
if tree_view.selected_id != "shipyards":
return _fail("a left click on a node selected \"%s\" instead of it" % tree_view.selected_id)
_click(shipyards, false)
print("a left click selects the node under it")
# --- B. a second click researches ----------------------------------
if game_state.is_unlocked("shipyards"):
return _fail("shipyards was already researched, so the confirm proves nothing")
_click(shipyards, true)
_click(shipyards, false)
if not game_state.is_unlocked("shipyards"):
return _fail("a second click on the selected node did not research it")
print("a second click on the same node researches it")
# --- C. held drag scrolls, a bare hover does not --------------------
# #219 removed the eight-deep doctrine lane and the tree now fits its own
# view, so there is nothing left to scroll. What is under test here is the
# INPUT rule - drag only while the button is held, wheel both ways - not
# how tall the tree happens to be, so the view is shortened to give the
# gesture something to move. Scrolled to the bottom first, the way the
# menu opens when the content does overflow.
var authored_height: float = tree_view.size.y
tree_view.size.y = authored_height * 0.4
tree_view.scroll_y = tree_view._max_scroll()
if tree_view.scroll_y <= 0.0:
return _fail("the tree still has nothing to scroll, so this proves nothing")
var before: float = tree_view.scroll_y
_move(Vector2(0.0, 120.0))
if not is_equal_approx(tree_view.scroll_y, before):
return _fail("the tree scrolled under a hovering pointer with no button held")
_click(_node_at("shipyards"), true)
_move(Vector2(0.0, 120.0))
var dragged: float = tree_view.scroll_y
if dragged >= before:
return _fail("holding the button and moving did not scroll the tree (%f -> %f)" \
% [before, dragged])
_click(_node_at("shipyards"), false)
_move(Vector2(0.0, 120.0))
if not is_equal_approx(tree_view.scroll_y, dragged):
return _fail("the tree kept scrolling after the button was released")
print("drag scrolls only while the button is held: %.0f -> %.0f" % [before, dragged])
# --- D. the wheel scrolls ------------------------------------------
_wheel(MOUSE_BUTTON_WHEEL_UP)
var wheeled: float = tree_view.scroll_y
if wheeled >= dragged:
return _fail("the wheel did not scroll the tree upward")
_wheel(MOUSE_BUTTON_WHEEL_DOWN)
if tree_view.scroll_y <= wheeled:
return _fail("the wheel did not scroll back down")
print("the wheel scrolls both ways")
# --- E. the panel has no dead band at the bottom --------------------
# Back to the authored height: section C shortened the view to have
# something to scroll, and the layout below is about the real one.
tree_view.size.y = authored_height
tree_view.scroll_y = 0.0
var detail: Control = scene.get_node("UI/SkillMenu/DetailLabel")
var status: Control = scene.get_node("UI/SkillMenu/StatusLabel")
var tree_bottom: float = tree_view.position.y + tree_view.size.y
var readout_gap: float = detail.position.y - tree_bottom
var floor_gap: float = skill_menu.size.y - (status.position.y + status.size.y)
print("tree %.0fpx tall, %.0fpx to the readout, %.0fpx of panel below it" \
% [tree_view.size.y, readout_gap, floor_gap])
if tree_view.size.y <= 0.0:
return _fail("the tree canvas has no height")
if readout_gap < 0.0:
return _fail("the tree canvas overlaps the readout below it")
if readout_gap > GAP_TOLERANCE:
return _fail("%.0fpx of unused panel between the tree and the readout" % readout_gap)
if floor_gap > GAP_TOLERANCE:
return _fail("%.0fpx of dead panel below the readout - the removed buy button's hole" \
% floor_gap)
print("TEST_SKILL_MENU: PASS")
quit(0)
return true
func _fail(message: String) -> bool:
print("TEST_SKILL_MENU: FAIL (%s)" % message)
quit(1)
return true