spaceship/tests/test_setup_editor_delete.gd
Loïc Wisniewski 6e13b6397c Take planets by killing what defends them
#211 conquest is the fight now: a planet registers the garrison its setup
authors, each defender's own death counts against it, and the last one takes
the world. The capture points - stand in a ring, wait out a timer, once per
ring - are gone, along with the editor palette that placed them.
#212 conquest opens a dialog on the surface naming the research points
recovered; the award travels with the signal, after it is banked.
#209 the prologue's charted sectors go with the breach, so the map the
survivors keep is of where they now are - and "chart 30 sectors" is no
longer complete before they have flown anywhere.
#218 enemy variety is gated on the FLEET, not on wave strength: a starting
fleet meets chasers only at any wave number, and each vessel past three
opens one more kind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
2026-09-05 13:31:25 +02:00

155 lines
6.7 KiB
GDScript

extends SceneTree
# Headless test: run with
# godot --headless -s res://tests/test_setup_editor_delete.gd
# The map editor could write setups but never remove one, so a mistyped name
# or an abandoned experiment stayed in the pool forever - and every planet of
# that type kept drawing it. It also listed setups in a font small enough to
# be awkward to hit on a phone.
#
# Deleting is destructive and the list is the only place it can be triggered
# from, so the panel is explicitly MODAL about it: a tap loads, unless the
# delete button has been pressed first, and the panel says which it is.
#
# Covers:
# A. the load panel lists saved setups and a tap loads one (unarmed)
# B. arming changes what a tap means, and says so in the title
# C. an armed tap deletes the file and drops it from the registry
# D. one tap deletes one setup - it disarms itself afterwards
# E. deleting the setup currently being edited leaves a fresh one open,
# rather than one whose next save would recreate the file
# F. the list is laid out at a size worth tapping at
const SCRATCH_ID: String = "test_delete_scratch"
const SCRATCH_PATH: String = "res://PlanetSetups/%s.tres" % SCRATCH_ID
const KEEP_ID: String = "test_delete_keeper"
const KEEP_PATH: String = "res://PlanetSetups/%s.tres" % KEEP_ID
const MIN_LIST_FONT_SIZE: int = 30
var editor: Node
var elapsed: float = 0.0
var stage: int = 0
func _initialize() -> void:
_remove(SCRATCH_PATH)
_remove(KEEP_PATH)
PlanetSetups.rescan()
editor = load("res://surface/PlanetSetupEditor.tscn").instantiate()
root.add_child(editor)
func _process(delta: float) -> bool:
elapsed += delta
if stage != 0:
return false
# _ready() (palette build, initial new_setup) needs a frame to flush -
# same hazard as tests/test_setup_editor.gd.
if elapsed < 0.1:
return false
stage = 1
# --- F. the list is tappable --------------------------------------------
var list: ItemList = editor.load_list
var font_size: int = list.get_theme_font_size("font_size")
if font_size < MIN_LIST_FONT_SIZE:
return _fail("the setup list draws at font size %d; %d or more is what makes the rows worth tapping at" \
% [font_size, MIN_LIST_FONT_SIZE])
print("setup list font size %d" % font_size)
# Two setups, so deleting one still leaves a populated list - a panel that
# empties would hide itself and mask a wrong-row deletion.
editor.new_setup(KEEP_ID, "Delete Keeper", "rocky")
editor.add_enemy("chaser", Vector2(10, 20))
if not editor.save_setup().ok:
return _fail("could not save the keeper setup")
editor.new_setup(SCRATCH_ID, "Delete Scratch", "rocky")
editor.add_enemy("chaser", Vector2(30, 40))
if not editor.save_setup().ok:
return _fail("could not save the scratch setup")
# --- A. the panel lists them, and an unarmed tap loads -------------------
editor._on_load_pressed()
if not editor.load_panel.visible:
return _fail("the load panel did not open")
if editor.load_title_label.text != "Load a setup":
return _fail("the panel opened in delete mode: '%s'" % editor.load_title_label.text)
var keep_index: int = Array(editor._load_list_setup_ids).find(KEEP_ID)
var scratch_index: int = Array(editor._load_list_setup_ids).find(SCRATCH_ID)
if keep_index < 0 or scratch_index < 0:
return _fail("the panel listed %s, missing one of the two just saved" \
% str(editor._load_list_setup_ids))
editor._on_load_list_item_selected(keep_index)
if String(editor.current_setup.setup_id) != KEEP_ID:
return _fail("an unarmed tap did not load '%s' (got '%s')" % [KEEP_ID, editor.current_setup.setup_id])
if editor.load_panel.visible:
return _fail("loading left the panel open")
print("unarmed tap loaded '%s' and closed the panel" % KEEP_ID)
# --- B. arming changes the meaning of a tap, visibly ---------------------
editor._on_load_pressed()
editor._on_delete_map_pressed()
if not editor._delete_map_armed:
return _fail("pressing the delete button did not arm delete mode")
if editor.load_title_label.text == "Load a setup":
return _fail("delete mode did not change the panel title, so nothing tells the player a tap now deletes")
print("armed: panel now reads '%s'" % editor.load_title_label.text)
# --- C. an armed tap deletes ---------------------------------------------
scratch_index = Array(editor._load_list_setup_ids).find(SCRATCH_ID)
editor._on_load_list_item_selected(scratch_index)
if FileAccess.file_exists(ProjectSettings.globalize_path(SCRATCH_PATH)):
return _fail("the setup file is still on disk after an armed tap")
for setup in PlanetSetups.all():
if String(setup.setup_id) == SCRATCH_ID:
return _fail("the registry still offers '%s' after deleting it" % SCRATCH_ID)
if Array(editor._load_list_setup_ids).find(KEEP_ID) < 0:
return _fail("deleting one setup also removed '%s' from the list" % KEEP_ID)
print("armed tap deleted '%s'; '%s' untouched" % [SCRATCH_ID, KEEP_ID])
# --- D. one tap, one deletion -------------------------------------------
if editor._delete_map_armed:
return _fail("delete mode stayed armed after a deletion, so the next tap would delete too")
if editor.load_title_label.text != "Load a setup":
return _fail("the panel still reads '%s' after disarming" % editor.load_title_label.text)
print("delete mode disarmed itself after one deletion")
# --- E. deleting what is open leaves a fresh setup ------------------------
editor.load_setup(KEEP_ID)
if String(editor.current_setup.setup_id) != KEEP_ID:
return _fail("could not reopen '%s' to test deleting the open setup" % KEEP_ID)
var outcome: Dictionary = editor.delete_setup(KEEP_ID)
if not outcome.ok:
return _fail("deleting the open setup failed: %s" % outcome.message)
if String(editor.current_setup.setup_id) == KEEP_ID:
return _fail("the editor is still holding '%s' after deleting it - the next save would put the file straight back" % KEEP_ID)
if not editor.current_setup.enemy_spawns.is_empty():
return _fail("the replacement setup carried the deleted one's enemies over")
print("deleting the open setup left a fresh '%s' being edited" % editor.current_setup.setup_id)
# Deleting something that is not there is refused, not silently "ok".
if editor.delete_setup(SCRATCH_ID).ok:
return _fail("deleting an already-deleted setup reported success")
_remove(SCRATCH_PATH)
_remove(KEEP_PATH)
PlanetSetups.rescan()
print("TEST_SETUP_EDITOR_DELETE: PASS")
quit(0)
return true
func _remove(res_path: String) -> void:
var path: String = ProjectSettings.globalize_path(res_path)
if FileAccess.file_exists(path):
DirAccess.remove_absolute(path)
if FileAccess.file_exists(path + ".uid"):
DirAccess.remove_absolute(path + ".uid")
func _fail(message: String) -> bool:
_remove(SCRATCH_PATH)
_remove(KEEP_PATH)
print("TEST_SETUP_EDITOR_DELETE: FAIL (%s)" % message)
quit(1)
return true