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