extends SceneTree ## Regression test for the Play -> Back navigation bug. ## ## The reported bug: after logging in and clicking Play, the user lands on ## world_list.tscn; clicking Back returns to main_menu.tscn, but the menu ## reset to a fully logged-out state (Play disabled, login form showing) ## even though ApiClient still held a valid token. main_menu.gd's _ready() ## used to unconditionally reset the UI on every scene load. ## ## This test drives the real navigation flow (change_scene_to_file) against ## a live backend: ## 1. Register a unique user so ApiClient holds a valid token. ## 2. Load main_menu.tscn and assert it restores the authenticated state. ## 3. Click Play -> world_list.tscn. ## 4. Click Back -> main_menu.tscn. ## 5. Assert the menu is STILL authenticated (Play enabled, welcome label ## actually visible in the tree, login/register buttons hidden). ## ## Run with: ## godot --headless --path . --script res://scripts/smoke_test_main_menu_back.gd ## ## Requires the backend to be running (default base URL ## `application/agp/api_base_url` -> http://127.0.0.1:8000). const USERNAME := "backnav_%d" const EMAIL := "backnav_%d@test.example.com" const PASSWORD := "BackNav123!" const DOB := "2000-01-01" var passed := 0 var failed := 0 func _initialize() -> void: _run() func _run() -> void: await process_frame var api := root.get_node("ApiClient") if api == null: printerr("FAIL: ApiClient autoload not found") quit(1) return # 1. Establish a real, valid session exactly the way a user would. randomize() var unique_id: int = int(Time.get_unix_time_from_system() * 1000) + randi() % 100000 var username := USERNAME % unique_id var email := EMAIL % unique_id print("\n=== Setup: register ", username, " ===") var register_result: Dictionary = await api.register(username, email, PASSWORD, DOB) if not _check("register succeeds", register_result.ok): print(" detail: ", register_result.detail) _finish() return _check("ApiClient stores token after register", api.has_token()) # 2. First visit to the main menu: should already show the authenticated # state (no login form, Play enabled). change_scene_to_file("res://scenes/main_menu.tscn") # change_scene_to_file defers the actual swap -- a couple frames is # plenty for the scene object itself to exist (unlike the real # get_me() network round-trip afterward, which is what auth_state_ready # actually waits for below). for i in 5: await process_frame if current_scene is Control: break var menu: Control = current_scene if menu == null or not menu is Control: printerr("FAIL: main_menu.tscn did not become current_scene") _finish() return await menu.auth_state_ready _check("first main menu visit restores authenticated state", _is_authenticated(menu)) # 3. Play -> world_list.tscn (real button press). var play_button: Button = menu.get_node("CenterContainer/VBoxContainer/PlayButton") play_button.pressed.emit() await _wait_for_scene_change(menu) var world_list_scene := current_scene if world_list_scene == null or world_list_scene == menu: printerr("FAIL: world_list.tscn did not become current_scene after Play") _finish() return # 4. Back -> main_menu.tscn (real button press). var back_button: Button = world_list_scene.get_node("VBoxContainer/HeaderHBox/BackButton") back_button.pressed.emit() await _wait_for_scene_change(world_list_scene) menu = current_scene if menu == null or not menu is Control: printerr("FAIL: main_menu.tscn did not become current_scene after Back") _finish() return # The new main_menu instance's own _ready() is *also* awaiting a real # get_me() call -- _wait_for_scene_change only confirms the scene # object exists, not that its authenticated-state fields have been # set yet. Wait for the real signal, not a guess. await menu.auth_state_ready # 5. THE regression assertion: still logged in after returning. _check("main menu stays authenticated after Play -> Back", _is_authenticated(menu)) _finish() func _is_authenticated(menu: Control) -> bool: var play_button: Button = menu.get_node("CenterContainer/VBoxContainer/PlayButton") var welcome_label: Label = menu.get_node("CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/WelcomeLabel") var auth_buttons_row: HBoxContainer = menu.get_node("CenterContainer/VBoxContainer/AuthButtonsRow") var auth_panel: PanelContainer = menu.get_node("CenterContainer/VBoxContainer/AuthPanel") _check("Play enabled", not play_button.disabled) # WelcomeLabel lives inside AuthPanel, so `.visible` alone is a # false-positive: the label can have visible=true while its parent is # hidden and it renders as nothing. `is_visible_in_tree()` is the # check that proves the user can actually see the welcome message. _check("Welcome label visible in tree", welcome_label.is_visible_in_tree()) _check("Auth buttons row hidden", not auth_buttons_row.visible) _check("Auth panel visible (it contains the welcome label)", auth_panel.visible) return (not play_button.disabled) \ and welcome_label.is_visible_in_tree() \ and not auth_buttons_row.visible \ and auth_panel.visible func _wait_for_scene_change(previous_scene: Node) -> void: for i in 20: await process_frame if current_scene != previous_scene and current_scene != null: return func _check(description: String, condition: bool) -> bool: if condition: print(" PASS: ", description) passed += 1 else: printerr(" FAIL: ", description) failed += 1 return condition func _finish() -> void: print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed]) quit(0 if failed == 0 else 1)