extends SceneTree ## Headless focus tests for the dev console — verifies the command line keeps (or ## reclaims) keyboard focus after the actions that were observed to drop it. ## Run: godot --headless --script tests/console_focus_test.gd ## ## Exit code 0 = all passed, 1 = any failure. var _passed: int = 0 var _failed: int = 0 func _init() -> void: _run.call_deferred() func _run() -> void: print("=".repeat(60)) print("CONSOLE FOCUS TESTS") print("=".repeat(60)) await test_focus_after_submit() await test_focus_after_external_steal() print("=".repeat(60)) print("Results: %d passed, %d failed" % [_passed, _failed]) print("=".repeat(60)) quit(1 if _failed > 0 else 0) func _assert_true(condition: bool, desc: String) -> void: if condition: _passed += 1 print(" PASS: %s" % desc) else: _failed += 1 print(" FAIL: %s" % desc) func _get_console() -> Node: var c: Node = root.get_node_or_null("/root/Console") if c == null: _assert_true(false, "Console autoload should exist") return c # Drive the real path: open via _toggle (pauses the tree, slide tween grabs focus), # type a command as real key events, submit with a real Enter, and confirm the guard # keeps the command line focused across the paused submit. func test_focus_after_submit() -> void: print("\n-- test_focus_after_submit --") var menu := _get_console() if menu == null: return menu._toggle() # Let the open slide tween finish and its grab_focus callback fire. for _i in 20: await process_frame _assert_true(paused, "tree paused while console open") _assert_true(menu._input_field.has_focus(), "input has focus after open") menu._input_field.text = "clear" menu._input_field.caret_column = 5 _press(KEY_ENTER) # The submit and any focus theft settle over the next few idle frames. for _i in 5: await process_frame _assert_true(menu._input_field.has_focus(), "input still has focus after submit") menu._toggle() for _i in 20: await process_frame # Push a key down+up through the viewport so LineEdit / _input handle it for real. func _press(keycode: Key) -> void: var down := InputEventKey.new() down.keycode = keycode down.physical_keycode = keycode down.pressed = true root.push_input(down) var up := InputEventKey.new() up.keycode = keycode up.physical_keycode = keycode up.pressed = false root.push_input(up) # Simulate anything yanking focus away mid-session (a stray click, a UI button); the # guard in _process must pull it straight back while the console is open. func test_focus_after_external_steal() -> void: print("\n-- test_focus_after_external_steal --") var menu := _get_console() if menu == null: return menu._is_open = true menu._panel.visible = true menu._input_field.grab_focus() await process_frame menu._input_field.release_focus() _assert_true(not menu._input_field.has_focus(), "focus released (precondition)") await process_frame _assert_true(menu._input_field.has_focus(), "guard reclaims focus next frame") menu._is_open = false menu._panel.visible = false