extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_build_info_popup.gd # Every build option explains itself before anything is spent. Picking a # structure costs ore and, for a mothership, can destroy the one you already # have, so no option may build straight off the menu press. # # Verifies, for EVERY option in the build menu: # 1. pressing it opens the popup with that building's own title and a # non-empty description, and shows its price # 2. nothing is built and no ore is spent until the confirm is pressed # 3. confirming builds exactly that structure and charges exactly its cost # 4. closing the popup instead drops the pending build entirely - a later # confirm press does nothing var scene: Node var game_state: Node var controller: Node var popup: Control var title: Label var message: Label var cost_number: NeonNumber var build_button: IconButton var stage: int = 0 var elapsed: float = 0.0 # Extractor is excluded: it needs a planet underneath, which # test_extractor_building covers in full. const OPTIONS: Array = [ {"type": "mothership", "title": "Mothership"}, {"type": "ship_crafter", "title": "Ship Crafter"}, {"type": "command_center", "title": "Command Center"}, ] func _initialize() -> void: game_state = root.get_node("GameState") game_state.rocks = 500 game_state.buildings.clear() game_state.extractors.clear() # Structures are gated behind the mothership's research tree. That gate # is test_skill_tree's subject, not this one's - here every option needs # to be available so the popup itself can be exercised. game_state.reset_progression() for node in GameState.SKILL_TREE: game_state.unlocked.append(node.id) scene = load("res://Main.tscn").instantiate() root.add_child(scene) controller = scene.get_node("BuildController") popup = scene.get_node("UI/BuildMenu/InfoPopup") title = popup.get_node("Panel/TitleLabel") message = popup.get_node("Panel/MessageLabel") cost_number = popup.get_node("Panel/CostNumber") build_button = popup.get_node("Panel/BuildButton") func _count(type: String) -> int: var n: int = 0 for b in game_state.buildings: if b.type == type: n += 1 return n func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.2: return false for option in OPTIONS: var type: String = option.type var expected_cost: int = controller.cost_of(type) var rocks_before: int = game_state.rocks var built_before: int = _count(type) controller._on_build_pressed() controller._show_building_info(type) var informed: bool = popup.visible \ and title.text == option.title \ and message.text.length() > 40 \ and cost_number.amount == expected_cost \ and build_button.visible var untouched: bool = game_state.rocks == rocks_before and _count(type) == built_before print("%s: popup=%s title='%s' cost=%d offered=%s nothing_spent_yet=%s" % [ type, popup.visible, title.text, cost_number.amount, build_button.visible, untouched ]) if not (informed and untouched): print("TEST_BUILD_INFO_POPUP: FAIL (%s did not explain itself before building)" % type) quit(1) return true # --- closing drops it, it must not build later ------------ controller._close_info() controller._confirm_build() if _count(type) != built_before or game_state.rocks != rocks_before: print("TEST_BUILD_INFO_POPUP: FAIL (%s built after the popup was dismissed)" % type) quit(1) return true # --- reopening and confirming builds it ------------------ controller._on_build_pressed() controller._show_building_info(type) controller._confirm_build() var built: bool = _count(type) == built_before + 1 var charged: bool = game_state.rocks == rocks_before - expected_cost print("%s: confirmed -> built=%s rocks %d->%d charged=%s" % [ type, built, rocks_before, game_state.rocks, charged ]) if not (built and charged): print("TEST_BUILD_INFO_POPUP: FAIL (%s did not build on confirmation)" % type) quit(1) return true print("TEST_BUILD_INFO_POPUP: PASS") quit(0) return true return false