NEO-131: Split reward HUD tests to satisfy gdlint limit.

pull/170/head
VinPropane 2026-06-07 23:07:18 -04:00
parent 7e3507088b
commit dc39250e96
4 changed files with 279 additions and 236 deletions

View File

@ -5,9 +5,6 @@ extends GdUnitTestSuite
const QuestHudController := preload("res://scripts/quest_hud_controller.gd") const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd") const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_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: class MockHttpTransport:
@ -78,15 +75,6 @@ class FailStartAcceptHttpTransport:
return ERR_UNAVAILABLE 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: func _make_progress_client(sync_transport: Node, accept_transport: Node = null) -> Node:
var c: Node = QuestProgressClient.new() var c: Node = QuestProgressClient.new()
c.set("injected_sync_http", sync_transport) c.set("injected_sync_http", sync_transport)
@ -487,226 +475,3 @@ func test_definitions_ready_repaints_with_display_names_when_progress_error() ->
# Assert # Assert
assert_str(progress_label.text).contains("Intro: Salvage Run") assert_str(progress_label.text).contains("Intro: Salvage Run")
assert_str(progress_label.text).not_contains("prototype_quest_gather_intro") 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")

View File

@ -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")

View File

@ -0,0 +1 @@
uid://d4n7q1rewardhudtest01

View File

@ -165,7 +165,7 @@ Combat intro completion is **skill XP only** in quest bundle — encounter token
| Test file | What it covers | | 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_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). **Regression:** existing NEO-122 GdUnit suites unchanged; `dotnet test` N/A (client-only).