extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_modal_blocks_input.gd # With a panel open, nothing behind it may react to a touch. # # Two separate leaks, because there are two separate input paths: # # 1. GUI clicks. The skill and formation panels cannot block these the # normal way (mouse_filter = STOP), because the skill tree and the # formation editor are driven from raw _unhandled_input, and a Control # that consumes touches through the GUI system swallows them before # they ever get there. So the panels stay transparent and the buttons # check whether they are behind one. # 2. Raw input. A touch landing on the panel but missing a skill node was # left unhandled, so it fell through to GameController - which sits on # the ROOT node and therefore sees everything its children ignore - and # moved the fleet or pinch-zoomed the world behind the menu. # # Verifies: # 1. a button outside an open panel does not fire # 2. a button INSIDE the open panel still does (or the panel is unusable) # 3. with every panel closed, that same outside button fires normally, so # a pass cannot just mean "the button never works" # 4. a touch on an open panel does not reach the world behind it var scene: Node var game_state: Node var fleet: Node var skill_menu: Control var map_button: Control var skill_close: Control var stage: int = 0 var elapsed: float = 0.0 var outside_fired: bool = false var inside_fired: bool = false func _initialize() -> void: game_state = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() game_state.transporters.clear() scene = load("res://Main.tscn").instantiate() root.add_child(scene) fleet = scene.get_node("Fleet") skill_menu = scene.get_node("UI/SkillMenu") map_button = scene.get_node("UI/MapButton") skill_close = scene.get_node("UI/SkillMenu/CloseButton") map_button.pressed.connect(func(): outside_fired = true) skill_close.pressed.connect(func(): inside_fired = true) func _tap(button: Control) -> void: # Straight at the button's own _gui_input, which is where a real tap # lands once Godot has routed it. var centre: Vector2 = button.size / 2.0 for pressed in [true, false]: var ev: InputEventScreenTouch = InputEventScreenTouch.new() ev.pressed = pressed ev.position = centre button._gui_input(ev) func _process(delta: float) -> bool: elapsed += delta match stage: 0: if elapsed < 0.2: return false scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) # --- 3. baseline: it works with nothing open ---------------- outside_fired = false _tap(map_button) if not outside_fired: print("TEST_MODAL_BLOCKS_INPUT: FAIL (the button never fires even with no panel open - the test proves nothing)") quit(1) return true print("no panel open -> outside button fires") # That tap actually OPENED the world map, which is itself a modal # panel - leave it open and it correctly blocks the research # panel's own buttons, which is a true result but not the one # being measured here. scene.get_node("UI/WorldMap").visible = false skill_menu.visible = true elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.2: return false # --- 1. behind an open panel -------------------------------- outside_fired = false _tap(map_button) if outside_fired: print("TEST_MODAL_BLOCKS_INPUT: FAIL (a button behind the open research panel still fired)") quit(1) return true print("research panel open -> button behind it is inert") # --- 2. inside it ------------------------------------------ inside_fired = false _tap(skill_close) if not inside_fired: print("TEST_MODAL_BLOCKS_INPUT: FAIL (the panel's own close button stopped working)") quit(1) return true print("panel's own button still works") elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.2: return false # --- 4. raw input must not reach the world ------------------ skill_menu.visible = true var before: Vector2 = fleet.fleet_target var controller: Node = scene.get_node("BuildController") var ev: InputEventScreenTouch = InputEventScreenTouch.new() ev.pressed = true ev.position = Vector2(360.0, 900.0) controller._unhandled_input(ev) if not root.get_viewport().is_input_handled(): print("TEST_MODAL_BLOCKS_INPUT: FAIL (a touch on the open panel was left unhandled - it falls through to the world behind)") quit(1) return true if fleet.fleet_target != before: print("TEST_MODAL_BLOCKS_INPUT: FAIL (the fleet moved from a touch on the panel)") quit(1) return true print("touch on the panel consumed, fleet target unchanged") print("TEST_MODAL_BLOCKS_INPUT: PASS") quit(0) return true return false