From dc39250e9616533b531baf732228a67288042cc4 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 7 Jun 2026 23:07:18 -0400 Subject: [PATCH] NEO-131: Split reward HUD tests to satisfy gdlint limit. --- client/test/quest_hud_controller_test.gd | 235 ------------------ client/test/quest_reward_hud_test.gd | 277 ++++++++++++++++++++++ client/test/quest_reward_hud_test.gd.uid | 1 + docs/plans/NEO-131-implementation-plan.md | 2 +- 4 files changed, 279 insertions(+), 236 deletions(-) create mode 100644 client/test/quest_reward_hud_test.gd create mode 100644 client/test/quest_reward_hud_test.gd.uid diff --git a/client/test/quest_hud_controller_test.gd b/client/test/quest_hud_controller_test.gd index bd936eb..d7a3338 100644 --- a/client/test/quest_hud_controller_test.gd +++ b/client/test/quest_hud_controller_test.gd @@ -5,9 +5,6 @@ extends GdUnitTestSuite 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") -const ItemDefsClient := preload("res://scripts/item_definitions_client.gd") - -const OPERATOR_CHAIN_QUEST_ID := "prototype_quest_operator_chain" class MockHttpTransport: @@ -78,15 +75,6 @@ class FailStartAcceptHttpTransport: return ERR_UNAVAILABLE -class StubItemDefsClient: - extends Node - - func display_name_for(item_id: String) -> String: - if item_id == "survey_drone_kit": - return "Survey Drone Kit" - return item_id - - func _make_progress_client(sync_transport: Node, accept_transport: Node = null) -> Node: var c: Node = QuestProgressClient.new() c.set("injected_sync_http", sync_transport) @@ -487,226 +475,3 @@ func test_definitions_ready_repaints_with_display_names_when_progress_error() -> # Assert assert_str(progress_label.text).contains("Intro: Salvage Run") assert_str(progress_label.text).not_contains("prototype_quest_gather_intro") - - -func _active_gather_snapshot() -> Dictionary: - return { - "schemaVersion": 1, - "playerId": "dev-local-1", - "quests": - [ - { - "questId": QuestHudController.GATHER_QUEST_ID, - "status": "active", - "currentStepIndex": 0, - "objectiveCounters": {"gather_intro_obj_scrap": 2}, - } - ], - } - - -func _completed_gather_with_summary_snapshot() -> Dictionary: - return { - "schemaVersion": 1, - "playerId": "dev-local-1", - "quests": - [ - { - "questId": QuestHudController.GATHER_QUEST_ID, - "status": "completed", - "currentStepIndex": 0, - "objectiveCounters": {}, - "completedAt": "2026-06-07T12:00:00Z", - "completionRewardSummary": - { - "itemGrants": [], - "skillXpGrants": [{"skillId": "salvage", "amount": 25}], - }, - } - ], - } - - -func test_completion_transition_paints_reward_label_with_skill_xp() -> void: - # Arrange - var controller: Node = QuestHudController.new() - var progress_label := Label.new() - var reward_label := Label.new() - auto_free(controller) - auto_free(progress_label) - auto_free(reward_label) - add_child(controller) - controller.set("_progress_label", progress_label) - controller.set("_reward_label", reward_label) - # Act - controller.call("_on_progress_received", _active_gather_snapshot()) - controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) - # Assert - assert_str(reward_label.text).contains("Quest rewards:") - assert_str(reward_label.text).contains("Salvage +25 XP") - - -func test_first_success_paints_reward_when_quest_was_active_in_session() -> void: - # Arrange - var controller: Node = QuestHudController.new() - var reward_label := Label.new() - auto_free(controller) - auto_free(reward_label) - add_child(controller) - controller.set("_reward_label", reward_label) - controller.call("_mark_quest_active_in_session", QuestHudController.GATHER_QUEST_ID) - # Act — first successful GET already completed (missed intermediate snapshots) - controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) - # Assert - assert_str(reward_label.text).contains("Salvage +25 XP") - - -func test_boot_completed_snapshot_does_not_paint_reward_label() -> void: - # Arrange - var controller: Node = QuestHudController.new() - var reward_label := Label.new() - auto_free(controller) - auto_free(reward_label) - add_child(controller) - controller.set("_reward_label", reward_label) - # Act - controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) - # Assert - assert_str(reward_label.text).is_equal("Quest rewards:\n—") - - -func _active_operator_chain_snapshot() -> Dictionary: - return { - "schemaVersion": 1, - "playerId": "dev-local-1", - "quests": - [ - { - "questId": OPERATOR_CHAIN_QUEST_ID, - "status": "active", - "currentStepIndex": 3, - "objectiveCounters": {}, - } - ], - } - - -func _completed_operator_chain_with_summary_snapshot() -> Dictionary: - return { - "schemaVersion": 1, - "playerId": "dev-local-1", - "quests": - [ - { - "questId": OPERATOR_CHAIN_QUEST_ID, - "status": "completed", - "currentStepIndex": 3, - "objectiveCounters": {}, - "completedAt": "2026-06-07T14:00:00Z", - "completionRewardSummary": - { - "itemGrants": [{"itemId": "survey_drone_kit", "quantity": 1}], - "skillXpGrants": [{"skillId": "salvage", "amount": 50}], - }, - } - ], - } - - -func test_completion_transition_paints_item_grant_with_display_name() -> void: - # Arrange - var controller: Node = QuestHudController.new() - var reward_label := Label.new() - var item_defs := StubItemDefsClient.new() - auto_free(controller) - auto_free(reward_label) - auto_free(item_defs) - add_child(controller) - add_child(item_defs) - controller.set("_reward_label", reward_label) - controller.set("_item_defs_client", item_defs) - # Act - controller.call("_on_progress_received", _active_operator_chain_snapshot()) - controller.call("_on_progress_received", _completed_operator_chain_with_summary_snapshot()) - # Assert - assert_str(reward_label.text).contains("Survey Drone Kit ×1") - assert_str(reward_label.text).contains("Salvage +50 XP") - - -func test_definitions_ready_repaints_reward_label_with_display_name() -> void: - # Arrange - var defs_transport := MockHttpTransport.new() - defs_transport.body_json = _gather_defs_json() - var defs_client: Node = _make_defs_client(defs_transport) - var controller: Node = QuestHudController.new() - var reward_label := Label.new() - auto_free(controller) - auto_free(reward_label) - add_child(controller) - controller.set("_defs_client", defs_client) - controller.set("_reward_label", reward_label) - controller.call("_on_progress_received", _active_gather_snapshot()) - controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) - assert_str(reward_label.text).contains(QuestHudController.GATHER_QUEST_ID) - # Act - defs_client.call("request_sync_from_server") - controller.call("_on_definitions_ready", []) - # Assert - assert_str(reward_label.text).contains("Intro: Salvage Run") - assert_str(reward_label.text).contains("Salvage +25 XP") - assert_str(reward_label.text).not_contains("prototype_quest_gather_intro") - - -func _survey_drone_item_defs_json() -> String: - return ( - '{"schemaVersion":1,"items":[{"id":"survey_drone_kit",' - + '"displayName":"Survey Drone Kit","inventorySlotKind":"bag"}]}' - ) - - -func test_item_definitions_ready_repaints_reward_item_display_name() -> void: - # Arrange - var item_transport := MockHttpTransport.new() - item_transport.body_json = _survey_drone_item_defs_json() - var item_defs: Node = ItemDefsClient.new() - item_defs.set("injected_http", item_transport) - auto_free(item_transport) - auto_free(item_defs) - add_child(item_defs) - await get_tree().process_frame - var controller: Node = QuestHudController.new() - var reward_label := Label.new() - auto_free(controller) - auto_free(reward_label) - add_child(controller) - controller.set("_reward_label", reward_label) - controller.set("_item_defs_client", item_defs) - controller.call("_on_progress_received", _active_operator_chain_snapshot()) - controller.call("_on_progress_received", _completed_operator_chain_with_summary_snapshot()) - assert_str(reward_label.text).contains("survey_drone_kit") - # Act - item_defs.call("request_sync_from_server") - controller.call("_on_item_definitions_ready", {}) - # Assert - assert_str(reward_label.text).contains("Survey Drone Kit ×1") - assert_str(reward_label.text).not_contains("survey_drone_kit ×1") - - -func test_sync_failure_shows_progress_error_and_idle_reward_label() -> void: - # Arrange - var controller: Node = QuestHudController.new() - var progress_label := Label.new() - var reward_label := Label.new() - auto_free(controller) - auto_free(progress_label) - auto_free(reward_label) - add_child(controller) - controller.set("_progress_label", progress_label) - controller.set("_reward_label", reward_label) - controller.set("_last_snapshot", _active_gather_snapshot()) - controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) - # Act - controller.call("_on_sync_failed", "HTTP 503") - # Assert - assert_str(progress_label.text).contains("error — HTTP 503") - assert_str(reward_label.text).is_equal("Quest rewards:\n—") diff --git a/client/test/quest_reward_hud_test.gd b/client/test/quest_reward_hud_test.gd new file mode 100644 index 0000000..0dae2df --- /dev/null +++ b/client/test/quest_reward_hud_test.gd @@ -0,0 +1,277 @@ +extends GdUnitTestSuite + +## NEO-131: quest completion reward label HUD tests. + +const QuestHudController := preload("res://scripts/quest_hud_controller.gd") +const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd") +const ItemDefsClient := preload("res://scripts/item_definitions_client.gd") + +const OPERATOR_CHAIN_QUEST_ID := "prototype_quest_operator_chain" + + +class MockHttpTransport: + extends Node + signal request_completed( + result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray + ) + 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, + 200, + PackedStringArray(), + body_json.to_utf8_buffer() + ) + return OK + + +class StubItemDefsClient: + extends Node + + func display_name_for(item_id: String) -> String: + if item_id == "survey_drone_kit": + return "Survey Drone Kit" + return item_id + + +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 _gather_defs_json() -> String: + return ( + '{"schemaVersion":1,"quests":[' + + '{"id":"prototype_quest_gather_intro","displayName":"Intro: Salvage Run",' + + '"prerequisiteQuestIds":[],"steps":[]}]}' + ) + + +func _active_gather_snapshot() -> Dictionary: + return { + "schemaVersion": 1, + "playerId": "dev-local-1", + "quests": + [ + { + "questId": QuestHudController.GATHER_QUEST_ID, + "status": "active", + "currentStepIndex": 0, + "objectiveCounters": {"gather_intro_obj_scrap": 2}, + } + ], + } + + +func _completed_gather_with_summary_snapshot() -> Dictionary: + return { + "schemaVersion": 1, + "playerId": "dev-local-1", + "quests": + [ + { + "questId": QuestHudController.GATHER_QUEST_ID, + "status": "completed", + "currentStepIndex": 0, + "objectiveCounters": {}, + "completedAt": "2026-06-07T12:00:00Z", + "completionRewardSummary": + { + "itemGrants": [], + "skillXpGrants": [{"skillId": "salvage", "amount": 25}], + }, + } + ], + } + + +func _active_operator_chain_snapshot() -> Dictionary: + return { + "schemaVersion": 1, + "playerId": "dev-local-1", + "quests": + [ + { + "questId": OPERATOR_CHAIN_QUEST_ID, + "status": "active", + "currentStepIndex": 3, + "objectiveCounters": {}, + } + ], + } + + +func _completed_operator_chain_with_summary_snapshot() -> Dictionary: + return { + "schemaVersion": 1, + "playerId": "dev-local-1", + "quests": + [ + { + "questId": OPERATOR_CHAIN_QUEST_ID, + "status": "completed", + "currentStepIndex": 3, + "objectiveCounters": {}, + "completedAt": "2026-06-07T14:00:00Z", + "completionRewardSummary": + { + "itemGrants": [{"itemId": "survey_drone_kit", "quantity": 1}], + "skillXpGrants": [{"skillId": "salvage", "amount": 50}], + }, + } + ], + } + + +func _survey_drone_item_defs_json() -> String: + return ( + '{"schemaVersion":1,"items":[{"id":"survey_drone_kit",' + + '"displayName":"Survey Drone Kit","inventorySlotKind":"bag"}]}' + ) + + +func test_completion_transition_paints_reward_label_with_skill_xp() -> void: + # Arrange + var controller: Node = QuestHudController.new() + var reward_label := Label.new() + auto_free(controller) + auto_free(reward_label) + add_child(controller) + controller.set("_reward_label", reward_label) + # Act + controller.call("_on_progress_received", _active_gather_snapshot()) + controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) + # Assert + assert_str(reward_label.text).contains("Quest rewards:") + assert_str(reward_label.text).contains("Salvage +25 XP") + + +func test_first_success_paints_reward_when_quest_was_active_in_session() -> void: + # Arrange + var controller: Node = QuestHudController.new() + var reward_label := Label.new() + auto_free(controller) + auto_free(reward_label) + add_child(controller) + controller.set("_reward_label", reward_label) + controller.call("_mark_quest_active_in_session", QuestHudController.GATHER_QUEST_ID) + # Act + controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) + # Assert + assert_str(reward_label.text).contains("Salvage +25 XP") + + +func test_boot_completed_snapshot_does_not_paint_reward_label() -> void: + # Arrange + var controller: Node = QuestHudController.new() + var reward_label := Label.new() + auto_free(controller) + auto_free(reward_label) + add_child(controller) + controller.set("_reward_label", reward_label) + # Act + controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) + # Assert + assert_str(reward_label.text).is_equal("Quest rewards:\n—") + + +func test_completion_transition_paints_item_grant_with_display_name() -> void: + # Arrange + var controller: Node = QuestHudController.new() + var reward_label := Label.new() + var item_defs := StubItemDefsClient.new() + auto_free(controller) + auto_free(reward_label) + auto_free(item_defs) + add_child(controller) + add_child(item_defs) + controller.set("_reward_label", reward_label) + controller.set("_item_defs_client", item_defs) + # Act + controller.call("_on_progress_received", _active_operator_chain_snapshot()) + controller.call("_on_progress_received", _completed_operator_chain_with_summary_snapshot()) + # Assert + assert_str(reward_label.text).contains("Survey Drone Kit ×1") + assert_str(reward_label.text).contains("Salvage +50 XP") + + +func test_definitions_ready_repaints_reward_label_with_display_name() -> void: + # Arrange + var defs_transport := MockHttpTransport.new() + defs_transport.body_json = _gather_defs_json() + var defs_client: Node = _make_defs_client(defs_transport) + var controller: Node = QuestHudController.new() + var reward_label := Label.new() + auto_free(controller) + auto_free(reward_label) + add_child(controller) + controller.set("_defs_client", defs_client) + controller.set("_reward_label", reward_label) + controller.call("_on_progress_received", _active_gather_snapshot()) + controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) + assert_str(reward_label.text).contains(QuestHudController.GATHER_QUEST_ID) + # Act + defs_client.call("request_sync_from_server") + controller.call("_on_definitions_ready", []) + # Assert + assert_str(reward_label.text).contains("Intro: Salvage Run") + assert_str(reward_label.text).contains("Salvage +25 XP") + assert_str(reward_label.text).not_contains("prototype_quest_gather_intro") + + +func test_item_definitions_ready_repaints_reward_item_display_name() -> void: + # Arrange + var item_transport := MockHttpTransport.new() + item_transport.body_json = _survey_drone_item_defs_json() + var item_defs: Node = ItemDefsClient.new() + item_defs.set("injected_http", item_transport) + auto_free(item_transport) + auto_free(item_defs) + add_child(item_defs) + await get_tree().process_frame + var controller: Node = QuestHudController.new() + var reward_label := Label.new() + auto_free(controller) + auto_free(reward_label) + add_child(controller) + controller.set("_reward_label", reward_label) + controller.set("_item_defs_client", item_defs) + controller.call("_on_progress_received", _active_operator_chain_snapshot()) + controller.call("_on_progress_received", _completed_operator_chain_with_summary_snapshot()) + assert_str(reward_label.text).contains("survey_drone_kit") + # Act + item_defs.call("request_sync_from_server") + controller.call("_on_item_definitions_ready", {}) + # Assert + assert_str(reward_label.text).contains("Survey Drone Kit ×1") + assert_str(reward_label.text).not_contains("survey_drone_kit ×1") + + +func test_sync_failure_shows_progress_error_and_idle_reward_label() -> void: + # Arrange + var controller: Node = QuestHudController.new() + var progress_label := Label.new() + var reward_label := Label.new() + auto_free(controller) + auto_free(progress_label) + auto_free(reward_label) + add_child(controller) + controller.set("_progress_label", progress_label) + controller.set("_reward_label", reward_label) + controller.set("_last_snapshot", _active_gather_snapshot()) + controller.call("_on_progress_received", _completed_gather_with_summary_snapshot()) + # Act + controller.call("_on_sync_failed", "HTTP 503") + # Assert + assert_str(progress_label.text).contains("error — HTTP 503") + assert_str(reward_label.text).is_equal("Quest rewards:\n—") diff --git a/client/test/quest_reward_hud_test.gd.uid b/client/test/quest_reward_hud_test.gd.uid new file mode 100644 index 0000000..c3cfaa3 --- /dev/null +++ b/client/test/quest_reward_hud_test.gd.uid @@ -0,0 +1 @@ +uid://d4n7q1rewardhudtest01 diff --git a/docs/plans/NEO-131-implementation-plan.md b/docs/plans/NEO-131-implementation-plan.md index 03b051e..95ef497 100644 --- a/docs/plans/NEO-131-implementation-plan.md +++ b/docs/plans/NEO-131-implementation-plan.md @@ -165,7 +165,7 @@ Combat intro completion is **skill XP only** in quest bundle — encounter token | Test file | What it covers | |-----------|----------------| | `client/test/quest_progress_client_test.gd` | **Add:** parse completed row with **`completionRewardSummary`** (empty **`itemGrants`**, skill XP row); helper returns summary dict. | -| `client/test/quest_hud_controller_test.gd` | **Add:** transition **`active` → `completed`** paints reward label with skill XP line; boot with already-**`completed`** does not paint; sync failure leaves progress error + reward **`—`**; operator-chain item grant + display name; **`definitions_ready`** reward label repaint. | +| `client/test/quest_reward_hud_test.gd` | **Add:** transition **`active` → `completed`** paints reward label with skill XP line; boot with already-**`completed`** does not paint; first-success-after-active; sync failure idle reward; operator-chain item grant + display name; quest/item **`definitions_ready`** reward repaint. | **Regression:** existing NEO-122 GdUnit suites unchanged; `dotnet test` N/A (client-only).