extends GdUnitTestSuite ## NEO-122: quest HUD controller accept key + progress label render. const QuestHudController := preload("res://scripts/quest_hud_controller.gd") const QuestProgressClient := preload("res://scripts/quest_progress_client.gd") const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd") class MockHttpTransport: extends Node signal request_completed( result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray ) var response_code: int = 200 var body_json: String = "" func request( _url: String, _custom_headers: PackedStringArray = PackedStringArray(), _method: HTTPClient.Method = HTTPClient.METHOD_GET, _request_data: String = "" ) -> Error: request_completed.emit( HTTPRequest.RESULT_SUCCESS, response_code, PackedStringArray(), body_json.to_utf8_buffer() ) return OK class NoopHttpTransport: extends Node signal request_completed( result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray ) func request( _url: String, _custom_headers: PackedStringArray = PackedStringArray(), _method: HTTPClient.Method = HTTPClient.METHOD_GET, _request_data: String = "" ) -> Error: return OK class PendingAcceptHttpTransport: extends Node signal request_completed( result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray ) func request( _url: String, _custom_headers: PackedStringArray = PackedStringArray(), _method: HTTPClient.Method = HTTPClient.METHOD_POST, _request_data: String = "" ) -> Error: return OK func _make_progress_client(sync_transport: Node, accept_transport: Node = null) -> Node: var c: Node = QuestProgressClient.new() c.set("injected_sync_http", sync_transport) var accept_http: Node = accept_transport if accept_transport != null else sync_transport c.set("injected_accept_http", accept_http) auto_free(sync_transport) if accept_transport != null and accept_transport != sync_transport: auto_free(accept_transport) auto_free(c) add_child(c) return c func _make_defs_client(transport: Node) -> Node: var c: Node = QuestDefinitionsClient.new() c.set("injected_http", transport) auto_free(transport) auto_free(c) add_child(c) return c func _build_controller( sync_transport: Node, accept_transport: Node, defs_transport: Node ) -> Dictionary: var controller: Node = QuestHudController.new() var progress: Node = _make_progress_client(sync_transport, accept_transport) var defs: Node = _make_defs_client(defs_transport) var progress_label := Label.new() var accept_label := Label.new() auto_free(controller) auto_free(progress_label) auto_free(accept_label) add_child(controller) await get_tree().process_frame controller.call( "setup", progress, defs, progress_label, accept_label, Callable(self, "_noop_http_config") ) await get_tree().process_frame return { "controller": controller, "progress": progress, "accept_label": accept_label, "progress_label": progress_label, } func _noop_http_config(_client: Node) -> void: pass func test_try_accept_key_input_handles_q_key() -> void: # Arrange var built: Dictionary = await _build_controller( NoopHttpTransport.new(), PendingAcceptHttpTransport.new(), NoopHttpTransport.new() ) var event := InputEventKey.new() event.pressed = true event.keycode = KEY_Q # Act var handled: bool = bool(built["controller"].call("try_accept_key_input", event)) # Assert assert_bool(handled).is_true() func test_progress_label_shows_loading_before_snapshot() -> void: # Arrange var built: Dictionary = await _build_controller( NoopHttpTransport.new(), NoopHttpTransport.new(), NoopHttpTransport.new() ) # Act var text: String = str(built["progress_label"].text) # Assert assert_str(text).contains("Quests:") assert_str(text).contains("Loading") func test_shift_q_shows_progress_loading_before_snapshot() -> void: # Arrange var controller: Node = QuestHudController.new() var accept_label := Label.new() var progress_client: Node = QuestProgressClient.new() var defs_client: Node = QuestDefinitionsClient.new() auto_free(controller) auto_free(accept_label) auto_free(progress_client) auto_free(defs_client) add_child(controller) controller.set("_progress_client", progress_client) controller.set("_defs_client", defs_client) controller.set("_accept_label", accept_label) controller.set("_last_snapshot", {}) controller.set("_progress_error", "") # Act controller.call("_request_eligible_accept") # Assert assert_str(accept_label.text).contains("progress loading") func test_shift_q_blocks_when_progress_sync_failed() -> void: # Arrange var controller: Node = QuestHudController.new() var accept_label := Label.new() var progress_client: Node = QuestProgressClient.new() var defs_client: Node = QuestDefinitionsClient.new() auto_free(controller) auto_free(accept_label) auto_free(progress_client) auto_free(defs_client) add_child(controller) controller.set("_progress_client", progress_client) controller.set("_defs_client", defs_client) controller.set("_accept_label", accept_label) controller.set("_last_snapshot", {}) controller.set("_progress_error", "HTTP 404 (player unknown)") # Act controller.call("_request_eligible_accept") # Assert assert_str(accept_label.text).contains("progress error") func test_request_accept_shows_busy_feedback() -> void: # Arrange var built: Dictionary = await _build_controller( NoopHttpTransport.new(), PendingAcceptHttpTransport.new(), NoopHttpTransport.new() ) var controller: Node = built["controller"] var accept_label: Label = built["accept_label"] controller.call("_request_accept", QuestHudController.GATHER_QUEST_ID) # Act var started: bool = bool(controller.call("_request_accept", QuestHudController.GATHER_QUEST_ID)) # Assert assert_bool(started).is_false() assert_str(accept_label.text).contains("busy") func _not_started_snapshot() -> Dictionary: return { "schemaVersion": 1, "playerId": "dev-local-1", "quests": [ { "questId": QuestHudController.GATHER_QUEST_ID, "status": "not_started", "currentStepIndex": 0, "objectiveCounters": {}, } ], } func test_accept_success_merges_quest_row_into_progress_label() -> void: # Arrange var progress_client: Node = _make_progress_client(NoopHttpTransport.new()) var controller: Node = QuestHudController.new() var progress_label := Label.new() auto_free(controller) auto_free(progress_label) add_child(controller) await get_tree().process_frame controller.set("_progress_client", progress_client) controller.set("_progress_label", progress_label) controller.set("_last_snapshot", _not_started_snapshot()) # Act ( controller . call( "_on_accept_result_received", QuestHudController.GATHER_QUEST_ID, { "accepted": true, "quest": { "questId": QuestHudController.GATHER_QUEST_ID, "status": "active", "currentStepIndex": 0, "objectiveCounters": {}, }, } ) ) # Assert assert_str(progress_label.text).contains("active step") func test_stale_sync_reapplies_accept_quest_patch() -> void: # Arrange var progress_client: Node = _make_progress_client(NoopHttpTransport.new()) var controller: Node = QuestHudController.new() var progress_label := Label.new() auto_free(controller) auto_free(progress_label) add_child(controller) await get_tree().process_frame controller.set("_progress_client", progress_client) controller.set("_progress_label", progress_label) controller.set("_last_snapshot", _not_started_snapshot()) ( controller . set( "_accept_quest_patch", { "questId": QuestHudController.GATHER_QUEST_ID, "status": "active", "currentStepIndex": 0, "objectiveCounters": {}, } ) ) # Act controller.call("_on_progress_received", _not_started_snapshot()) # Assert assert_str(progress_label.text).contains("active step") func test_accept_clears_progress_error_and_shows_active_row() -> void: # Arrange var progress_client: Node = _make_progress_client(NoopHttpTransport.new()) var controller: Node = QuestHudController.new() var progress_label := Label.new() auto_free(controller) auto_free(progress_label) add_child(controller) await get_tree().process_frame controller.set("_progress_client", progress_client) controller.set("_progress_label", progress_label) controller.set("_last_snapshot", _not_started_snapshot()) controller.set("_progress_error", "HTTP 404 (player unknown)") # Act ( controller . call( "_on_accept_result_received", QuestHudController.GATHER_QUEST_ID, { "accepted": true, "quest": { "questId": QuestHudController.GATHER_QUEST_ID, "status": "active", "currentStepIndex": 0, "objectiveCounters": {}, }, } ) ) # Assert assert_str(progress_label.text).contains("active step") assert_str(progress_label.text).not_contains("error —") func test_sync_fail_preserves_accept_quest_patch() -> void: # Arrange var progress_client: Node = _make_progress_client(NoopHttpTransport.new()) var controller: Node = QuestHudController.new() var progress_label := Label.new() auto_free(controller) auto_free(progress_label) add_child(controller) await get_tree().process_frame controller.set("_progress_client", progress_client) controller.set("_progress_label", progress_label) ( controller . set( "_accept_quest_patch", { "questId": QuestHudController.GATHER_QUEST_ID, "status": "active", "currentStepIndex": 0, "objectiveCounters": {}, } ) ) ( controller . set( "_last_snapshot", ( QuestProgressClient . merge_quest_row_into_snapshot( _not_started_snapshot(), { "questId": QuestHudController.GATHER_QUEST_ID, "status": "active", "currentStepIndex": 0, "objectiveCounters": {}, } ) ) ) ) # Act controller.call("_on_sync_failed", "HTTP 503") # Assert assert_str(progress_label.text).contains("active step") assert_str(progress_label.text).contains("sync error — HTTP 503") func test_shift_q_shows_definitions_error_when_defs_failed() -> void: # Arrange var controller: Node = QuestHudController.new() var accept_label := Label.new() var progress_client: Node = QuestProgressClient.new() var defs_client: Node = QuestDefinitionsClient.new() auto_free(controller) auto_free(accept_label) auto_free(progress_client) auto_free(defs_client) add_child(controller) controller.set("_progress_client", progress_client) controller.set("_defs_client", defs_client) controller.set("_accept_label", accept_label) controller.set("_last_snapshot", _not_started_snapshot()) controller.set("_progress_error", "") controller.set("_defs_error", "HTTP 503") # Act controller.call("_request_eligible_accept") # Assert assert_str(accept_label.text).contains("definitions error") func _gather_defs_json() -> String: return ( '{"schemaVersion":1,"quests":[' + '{"id":"prototype_quest_gather_intro","displayName":"Intro: Salvage Run",' + '"prerequisiteQuestIds":[],"steps":[]}]}' ) func test_definitions_ready_repaints_with_display_names_when_progress_error() -> void: # Arrange var defs_transport := MockHttpTransport.new() defs_transport.body_json = _gather_defs_json() var defs_client: Node = _make_defs_client(defs_transport) var progress_client: Node = _make_progress_client(NoopHttpTransport.new()) var controller: Node = QuestHudController.new() var progress_label := Label.new() auto_free(controller) auto_free(progress_label) add_child(controller) await get_tree().process_frame defs_client.call("request_sync_from_server") controller.set("_progress_client", progress_client) controller.set("_defs_client", defs_client) controller.set("_progress_label", progress_label) controller.set("_last_snapshot", _not_started_snapshot()) controller.set("_progress_error", "HTTP 503") # Act controller.call("_on_definitions_ready", []) # Assert assert_str(progress_label.text).contains("Intro: Salvage Run") assert_str(progress_label.text).not_contains("prototype_quest_gather_intro")