spaceship/tests/test_main_menu.gd
2026-09-02 13:43:56 +02:00

156 lines
6.4 KiB
GDScript

extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_main_menu.gd
# Verifies:
# 1. the menu loads with Play/Map Editor/Quit; Map Editor is enabled and
# reaches PlanetSetupEditor.tscn (#48)
# 2. there is no Quit entry in any build - a web build has nothing to quit
# to, and Android's back gesture already does it
# 3. Play resets GameState to a fresh game AND reaches Main.tscn
# 4. #169 - a press puts a loading screen up FIRST and swaps the scene
# afterwards. A button that does nothing for several seconds reads as a
# button that was not pressed, which is how the web build reads; the
# order is the whole fix, so it is the thing asserted.
# 5. #169 - a second press while a load is running is ignored, or an
# impatient double tap builds the world twice (on the web a button fires
# on PRESS, so that tap costs nothing to make)
# 6. #174 - the opaque loading cover fills the viewport and puts its
# activity bar at the middle of the screen
var menu: Node
var game_state: Node
var elapsed: float = 0.0
var stage: int = 0
func _initialize() -> void:
game_state = root.get_node("GameState")
menu = load("res://ui/MainMenu.tscn").instantiate()
root.add_child(menu)
current_scene = menu
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
# A scene added to the tree inside _initialize() has not had its
# _ready() flushed yet - see test_planet_setups.gd for the same
# hazard - so the menu's @onready buttons aren't valid until a
# frame has actually processed.
if elapsed < 0.1:
return false
if not menu.editor_button.enabled:
return _fail("map editor button is not enabled (#48 shipped)")
# Both development entries follow the same rule, and the rule is
# what is under test rather than the concrete boolean - this run
# is from Godot, so it exercises the "shown" branch.
var developing: bool = menu.is_developer_build()
if menu.editor_button.visible != developing or menu.editor_label.visible != developing:
return _fail("map editor entry visibility did not follow is_developer_build()")
if menu.dev_button.visible != developing or menu.dev_label.visible != developing:
return _fail("dev mode entry visibility did not follow is_developer_build()")
if menu.has_node("UI/QuitButton") or menu.has_node("UI/QuitLabel"):
return _fail("the menu still carries a Quit entry")
_press(menu.editor_button)
# --- 4. the cover goes up before anything blocks --------------
if menu.loading_path != "res://surface/PlanetSetupEditor.tscn":
return _fail("the press did not start a load (loading_path %s)" % menu.loading_path)
if menu.loading_screen == null or not menu.loading_screen.is_inside_tree():
return _fail("the press started a load with no loading screen up")
if current_scene != menu:
return _fail("the scene swapped inside the press, so nothing was ever shown")
var viewport_size: Vector2 = menu.get_viewport().get_visible_rect().size
if menu.loading_screen.size.distance_to(viewport_size) > 1.0:
return _fail("loading cover is %s, viewport is %s" \
% [menu.loading_screen.size, viewport_size])
if menu.loading_screen.BACKDROP.a < 0.999:
return _fail("loading cover is translucent (alpha %.2f)" \
% menu.loading_screen.BACKDROP.a)
var bar: Rect2 = menu.loading_screen._bar_rect()
if absf(bar.get_center().y - viewport_size.y * 0.5) > 1.0:
return _fail("loading bar centre %.1f is not screen centre %.1f" \
% [bar.get_center().y, viewport_size.y * 0.5])
print("pressing an entry raises the loading screen and leaves the menu up")
print("loading cover fills %s and centres its bar" % viewport_size)
# --- 5. and a second press changes nothing --------------------
var screen: Node = menu.loading_screen
_press(menu.editor_button)
_press(menu.play_button)
if menu.loading_screen != screen \
or menu.loading_path != "res://surface/PlanetSetupEditor.tscn":
return _fail("a second press while loading started another scene")
print("further presses while a load is running are ignored")
elapsed = 0.0
stage = 1
return false
1:
# Waited on rather than timed: the load is spread over frames now,
# and how many it takes is a property of the machine.
var on_editor: bool = current_scene != null and current_scene.name == "PlanetSetupEditor"
if not on_editor:
if elapsed < 30.0:
return false
return _fail("pressing Map Editor did not reach PlanetSetupEditor.tscn in 30s")
print("the load finished and the scene swapped")
# Return to the menu the same way the player would, then test Play.
menu = load("res://ui/MainMenu.tscn").instantiate()
root.add_child(menu)
current_scene = menu
elapsed = 0.0
stage = 2
return false
2:
if elapsed < 0.1:
return false
# Dirty the game state exactly like an in-progress run would,
# so a genuine reset is distinguishable from a coincidental match.
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 999)
game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
game_state.surviving_ship_count = 1
game_state.planet_capture["chunk:1:1"] = {"captured": [true], "conquered": true}
_press(menu.play_button)
elapsed = 0.0
stage = 3
return false
3:
var on_main: bool = current_scene != null and current_scene.name == "Main"
if not on_main and elapsed < 30.0:
return false
# A normal game now starts with NOTHING banked - the opening
# mining run is the tutorial, and 500 free ore skipped it.
var reset_ok: bool = game_state.rocks == 0 and game_state.buildings.is_empty() \
and game_state.surviving_ship_count == GameState.STARTING_SHIP_COUNT \
and game_state.planet_capture.is_empty()
print("on_main=%s reset_ok=%s rocks=%d buildings=%d ship_count=%d" % [
on_main, reset_ok, game_state.rocks, game_state.buildings.size(),
game_state.surviving_ship_count
])
if not on_main or not reset_ok:
return _fail("Play did not reset to a fresh game before reaching Main.tscn")
print("TEST_MAIN_MENU: PASS")
quit(0)
return true
return false
func _press(button: IconButton) -> void:
var down: InputEventScreenTouch = InputEventScreenTouch.new()
down.pressed = true
button._gui_input(down)
var up: InputEventScreenTouch = InputEventScreenTouch.new()
up.pressed = false
button._gui_input(up)
func _fail(message: String) -> bool:
print("TEST_MAIN_MENU: FAIL (%s)" % message)
quit(1)
return true