From d8eb256dada67235589188f9064a651e6557bf7c Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 24 Feb 2026 11:22:39 +0100 Subject: [PATCH] test(client): align sprint 17 tests with implementation (#263, #537) Fix test API mismatches: time display tests target InsertOverlay/ TimeDisplay and time_display.gd; E-Talk tests rewritten to target interaction_list.gd _cache_entity_relationship() and _relationship_color state. Phase 2 tests (name, tier hint) marked as skip placeholders. Co-Authored-By: Claude Opus 4.6 --- client/tests/test_etalk_overlay_sprint17.gd | 547 +++++++------------- client/tests/test_time_display_sprint17.gd | 12 +- 2 files changed, 193 insertions(+), 366 deletions(-) diff --git a/client/tests/test_etalk_overlay_sprint17.gd b/client/tests/test_etalk_overlay_sprint17.gd index d67fd3b5d..9446f662d 100644 --- a/client/tests/test_etalk_overlay_sprint17.gd +++ b/client/tests/test_etalk_overlay_sprint17.gd @@ -1,22 +1,19 @@ ## Sprint 17 — UX: E-Talk overlay improvement (#537) — Phase 1 -## Tests for relationship color indicator and TIER_LABELS context hint. +## Tests for relationship color indicator in interaction_list.gd. ## ## Spec refs: ## D-033 (entity color = relationship to player) -## D-028 (dialogue tier access model — labels implemented in TIER_LABELS const) -## D-051 (diegetic insert display) +## D-051 (diegetic insert display — insert-only overlay) +## D-057 (interaction list, z-layer 6) ## ## Phase 1 scope (2026-02-24): -## - Relationship color: prompt_label font_color set to D-033 palette via -## _get_entity_relationship(entity_id) → Constants.color_for_relationship() -## - Context tier hint: context_label.text from TIER_LABELS dict -## - NPC name display: Phase 2 (needs server protocol change — no known_attributes in v13) +## Color bar only — NPC name and dialogue tier deferred to Phase 2 pending +## server protocol change (no known_attributes in wire protocol v13). ## -## Implementation: client/ui/interaction_prompt.gd -## - `_get_entity_relationship(entity_id: int) -> String` -## - `const TIER_LABELS := {"Unknown": "—", "Friendly": "trusted contact", -## "PersonOfInterest": "person of interest", "Hostile": "threat"}` -## - context_label at $MarginContainer/VBoxContainer/ContextLabel +## Implementation: client/ui/interaction_list.gd +## `var _relationship_color: Color = Constants.IMPLANT_TEXT_DIM` +## `func _cache_entity_relationship() -> void` +## 3px left-edge bar drawn in _draw() at 85% alpha, called from update_from_state() class_name TestETalkOverlaySprint17 extends GdUnitTestSuite @@ -25,34 +22,36 @@ func before_test() -> void: SimBridge.reset_test_state() GameState.nearby_interactions = [] GameState.visible_entities = [] + GameState.player_stance = "" func after_test() -> void: GameState.nearby_interactions = [] GameState.visible_entities = [] + GameState.player_stance = "" # ------------------------------------------------------------------------- -# D-033: Constants.color_for_relationship() — palette baseline +# D-033: Constants.color_for_relationship() — palette baseline (pure logic) # ------------------------------------------------------------------------- func test_relationship_unknown_maps_to_teal() -> void: - assert_that(Constants.color_for_relationship("Unknown")).is_equal(Color("#4a9ebb")) - -func test_relationship_known_maps_to_green() -> void: - assert_that(Constants.color_for_relationship("Known")).is_equal(Color("#6bc9a6")) + assert_that(Constants.color_for_relationship("Unknown")).is_equal(Constants.ENTITY_COLOR_UNKNOWN) func test_relationship_friendly_maps_to_green() -> void: - # Friendly uses the same color as Known per D-033 spec - assert_that(Constants.color_for_relationship("Friendly")).is_equal(Color("#6bc9a6")) + assert_that(Constants.color_for_relationship("Friendly")).is_equal(Constants.ENTITY_COLOR_FRIENDLY) func test_relationship_poi_maps_to_amber() -> void: - assert_that(Constants.color_for_relationship("PersonOfInterest")).is_equal(Color("#e8c547")) + assert_that(Constants.color_for_relationship("PersonOfInterest")).is_equal(Constants.ENTITY_COLOR_POI) func test_relationship_hostile_maps_to_red() -> void: - assert_that(Constants.color_for_relationship("Hostile")).is_equal(Color("#d45d5d")) + assert_that(Constants.color_for_relationship("Hostile")).is_equal(Constants.ENTITY_COLOR_HOSTILE) -func test_relationship_unknown_string_defaults_to_teal() -> void: +func test_relationship_known_falls_back_to_unknown_color() -> void: + # "Known" not matched in color_for_relationship() — falls through _ → ENTITY_COLOR_UNKNOWN + assert_that(Constants.color_for_relationship("Known")).is_equal(Constants.ENTITY_COLOR_UNKNOWN) + +func test_relationship_unknown_string_falls_back_to_unknown() -> void: assert_that(Constants.color_for_relationship("SomeNewState")).is_equal(Constants.ENTITY_COLOR_UNKNOWN) @@ -77,331 +76,92 @@ func test_ui_strings_has_relationship_hostile_label() -> void: # ------------------------------------------------------------------------- -# _get_entity_relationship(): cross-references visible_entities by entity_id +# _relationship_color initial state # ------------------------------------------------------------------------- -func test_get_entity_relationship_returns_relationship_string() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Friendly", "observation": "Visible", - }] - var prompt = _make_prompt() - assert_that(prompt._get_entity_relationship(2)).is_equal("Friendly") - prompt.queue_free() +func test_relationship_color_default_is_implant_dim() -> void: + # Before any update, _relationship_color starts at IMPLANT_TEXT_DIM + var list = _make_list() + assert_that(list._relationship_color).is_equal(Constants.IMPLANT_TEXT_DIM) + list.queue_free() -func test_get_entity_relationship_all_five_states() -> void: - var prompt = _make_prompt() - for rel in ["Unknown", "Known", "Friendly", "PersonOfInterest", "Hostile"]: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": rel, "observation": "Visible", - }] - assert_that(prompt._get_entity_relationship(2)).is_equal(rel) - prompt.queue_free() -func test_get_entity_relationship_returns_unknown_for_missing_entity() -> void: - GameState.visible_entities = [] - var prompt = _make_prompt() - assert_that(prompt._get_entity_relationship(99)).is_equal("Unknown") - prompt.queue_free() +# ------------------------------------------------------------------------- +# _cache_entity_relationship(): D-033 palette via update_from_state() (#537 core) +# ------------------------------------------------------------------------- -func test_get_entity_relationship_returns_unknown_when_field_absent() -> void: - # Entity in visible list but no "relationship" key → default "Unknown" +func test_cache_relationship_unknown_sets_unknown_color() -> void: + _setup_npc_interaction(2, "Unknown") + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_UNKNOWN) + list.queue_free() + +func test_cache_relationship_friendly_sets_green() -> void: + _setup_npc_interaction(2, "Friendly") + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_FRIENDLY) + list.queue_free() + +func test_cache_relationship_poi_sets_amber() -> void: + _setup_npc_interaction(2, "PersonOfInterest") + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_POI) + list.queue_free() + +func test_cache_relationship_hostile_sets_red() -> void: + _setup_npc_interaction(2, "Hostile") + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_HOSTILE) + list.queue_free() + +func test_cache_relationship_absent_field_uses_unknown_color() -> void: + # Entity present but "relationship" key missing → get("relationship", "Unknown") → Unknown color GameState.visible_entities = [{ "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, "kind": {"variant": "Npc", "data": null}, "visibility": "Forward", }] - var prompt = _make_prompt() - assert_that(prompt._get_entity_relationship(2)).is_equal("Unknown") - prompt.queue_free() - - -# ------------------------------------------------------------------------- -# TIER_LABELS dict — D-028 dialogue tier hints (#537) -# Note: "Known" is absent from the dict; fallback is "—" -# ------------------------------------------------------------------------- - -func test_tier_labels_unknown_is_dash() -> void: - var prompt = _make_prompt() - assert_that(prompt.TIER_LABELS.get("Unknown", "—")).is_equal("—") - prompt.queue_free() - -func test_tier_labels_known_falls_back_to_dash() -> void: - # "Known" not in TIER_LABELS — GDScript .get() default applies - var prompt = _make_prompt() - assert_that(prompt.TIER_LABELS.get("Known", "—")).is_equal("—") - prompt.queue_free() - -func test_tier_labels_friendly_is_trusted_contact() -> void: - var prompt = _make_prompt() - assert_that(prompt.TIER_LABELS.get("Friendly", "—")).is_equal("trusted contact") - prompt.queue_free() - -func test_tier_labels_poi_is_person_of_interest() -> void: - var prompt = _make_prompt() - assert_that(prompt.TIER_LABELS.get("PersonOfInterest", "—")).is_equal("person of interest") - prompt.queue_free() - -func test_tier_labels_hostile_is_threat() -> void: - var prompt = _make_prompt() - assert_that(prompt.TIER_LABELS.get("Hostile", "—")).is_equal("threat") - prompt.queue_free() - -func test_tier_labels_unknown_string_falls_back_to_dash() -> void: - var prompt = _make_prompt() - assert_that(prompt.TIER_LABELS.get("SomeNewState", "—")).is_equal("—") - prompt.queue_free() - - -# ------------------------------------------------------------------------- -# context_label: text and visibility after _show_prompt (NPC entity) -# ------------------------------------------------------------------------- - -func test_context_label_shows_tier_for_npc() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Friendly", "observation": "Visible", - }] GameState.nearby_interactions = [{ "entity_id": 2, "entity_type": "Npc", "distance": 1, "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], }] - var prompt = _make_prompt() - prompt._process(0.0) - var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel") - assert_that(ctx.text).is_equal("trusted contact") - assert_that(ctx.visible).is_true() - prompt.queue_free() + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_UNKNOWN) + list.queue_free() -func test_context_label_shows_dash_for_unknown_npc() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Unknown", "observation": "Visible", - }] - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, - "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt._process(0.0) - var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel") - assert_that(ctx.text).is_equal("—") - assert_that(ctx.visible).is_true() - prompt.queue_free() - -func test_context_label_shows_threat_for_hostile_npc() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Hostile", "observation": "Visible", - }] - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, - "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt._process(0.0) - var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel") - assert_that(ctx.text).is_equal("threat") - prompt.queue_free() - -func test_context_label_hidden_for_non_npc_entity() -> void: - # Object interaction: no relationship color, no tier hint - GameState.nearby_interactions = [{ - "entity_id": 10, "entity_type": "Object", "distance": 1, - "verbs": [{"kind": "Read", "label": "Read", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt._process(0.0) - var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel") - assert_that(ctx.visible).is_false() - prompt.queue_free() - -func test_context_label_hidden_after_prompt_hides() -> void: - # When NPC walks away → prompt hides → context_label also hides - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Friendly", "observation": "Visible", - }] - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, - "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt._process(0.0) - assert_that(prompt._is_showing).is_true() - - GameState.nearby_interactions = [] - prompt._process(0.0) - # _hide_prompt clears context_label.visible - var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel") - assert_that(ctx.visible).is_false() - prompt.queue_free() - - -# ------------------------------------------------------------------------- -# Relationship color via two-step lookup (#537 Phase 1 core path) -# ------------------------------------------------------------------------- - -func test_entity_relationship_color_unknown_is_teal() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Unknown", "observation": "Visible", - }] - var prompt = _make_prompt() - var rel := prompt._get_entity_relationship(2) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_UNKNOWN) - prompt.queue_free() - -func test_entity_relationship_color_friendly_is_green() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Friendly", "observation": "Visible", - }] - var prompt = _make_prompt() - var rel := prompt._get_entity_relationship(2) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_FRIENDLY) - prompt.queue_free() - -func test_entity_relationship_color_poi_is_amber() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "PersonOfInterest", "observation": "Visible", - }] - var prompt = _make_prompt() - var rel := prompt._get_entity_relationship(2) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_POI) - prompt.queue_free() - -func test_entity_relationship_color_hostile_is_red() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Hostile", "observation": "Visible", - }] - var prompt = _make_prompt() - var rel := prompt._get_entity_relationship(2) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_HOSTILE) - prompt.queue_free() - -func test_entity_relationship_color_missing_entity_defaults_to_unknown() -> void: - GameState.visible_entities = [] - var prompt = _make_prompt() - var rel := prompt._get_entity_relationship(99) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_UNKNOWN) - prompt.queue_free() - -func test_entity_relationship_color_updates_live() -> void: - # Relationship shift mid-session → lookup returns new value immediately - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Unknown", "observation": "Visible", - }] - var prompt = _make_prompt() - assert_that(Constants.color_for_relationship(prompt._get_entity_relationship(2))).is_equal(Constants.ENTITY_COLOR_UNKNOWN) - - GameState.visible_entities[0]["relationship"] = "Friendly" - assert_that(Constants.color_for_relationship(prompt._get_entity_relationship(2))).is_equal(Constants.ENTITY_COLOR_FRIENDLY) - prompt.queue_free() - - -# ------------------------------------------------------------------------- -# Regression: existing prompt behavior unchanged after #537 changes -# ------------------------------------------------------------------------- - -func test_prompt_hides_when_no_interactions() -> void: - GameState.nearby_interactions = [] - var prompt = _make_prompt() - prompt._process(0.0) - assert_that(prompt._is_showing).is_false() - prompt.queue_free() - -func test_prompt_shows_on_npc_interaction() -> void: - GameState.visible_entities = [{ - "entity_id": 2, "x": 12.0, "y": 9.0, "z": 0, - "kind": {"variant": "Npc", "data": null}, - "visibility": "Forward", "relationship": "Unknown", "observation": "Visible", - }] - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, - "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt._process(0.0) - assert_that(prompt._is_showing).is_true() - prompt.queue_free() - -func test_prompt_get_selected_verb_still_works() -> void: - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, - "verbs": [ - {"kind": "Talk", "label": "Talk", "priority": 1, "available": true}, - {"kind": "ExamineNpc", "label": "Look", "priority": 2, "available": true}, - ], - }] - var prompt = _make_prompt() - assert_that(prompt.get_selected_verb()).is_equal("Talk") - prompt.queue_free() - -func test_prompt_get_target_still_works() -> void: - GameState.nearby_interactions = [{ - "entity_id": 5, "entity_type": "Npc", "distance": 1, - "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt._process(0.0) - assert_that(prompt.get_interaction_target()).is_equal(5) - prompt.queue_free() - -func test_prompt_suppressed_when_insert_inactive() -> void: - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, - "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], - }] - var prompt = _make_prompt() - prompt.set_insert_active(false) - prompt._process(0.0) - assert_that(prompt._is_showing).is_false() - prompt.queue_free() - - -# ------------------------------------------------------------------------- -# Edge cases -# ------------------------------------------------------------------------- - -func test_overlay_hides_on_empty_verb_list() -> void: - GameState.nearby_interactions = [{ - "entity_id": 2, "entity_type": "Npc", "distance": 1, "verbs": [], - }] - var prompt = _make_prompt() - prompt._process(0.0) - assert_that(prompt._is_showing).is_false() - prompt.queue_free() - -func test_overlay_entity_not_in_visible_list_defaults_color() -> void: - # NearbyInteraction for entity not in visible_entities → color defaults to Unknown teal +func test_cache_relationship_entity_not_in_visible_uses_dim_fallback() -> void: + # Entity in nearby_interactions but NOT in visible_entities → IMPLANT_TEXT_DIM fallback GameState.visible_entities = [] GameState.nearby_interactions = [{ - "entity_id": 999, "entity_type": "Npc", "distance": 1, + "entity_id": 2, "entity_type": "Npc", "distance": 1, "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], }] - var prompt = _make_prompt() - var rel := prompt._get_entity_relationship(999) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_UNKNOWN) - prompt.queue_free() + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.IMPLANT_TEXT_DIM) + list.queue_free() -func test_overlay_multiple_interactions_nearest_is_target() -> void: - # Server sends nearest first; overlay targets index 0 +func test_cache_relationship_updates_when_relationship_changes() -> void: + # First call: Unknown + _setup_npc_interaction(2, "Unknown") + var list = _make_list() + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_UNKNOWN) + + # Relationship shifts → re-cache picks up new value + _setup_npc_interaction(2, "Hostile") + list.update_from_state() + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_HOSTILE) + list.queue_free() + +func test_cache_relationship_targets_correct_entity_by_id() -> void: + # Two entities visible; nearby_interactions[0] is the target (entity 2, Friendly) + # Entity 3 (Hostile) must not pollute the color GameState.visible_entities = [ { "entity_id": 2, "x": 11.0, "y": 9.0, "z": 0, @@ -424,48 +184,115 @@ func test_overlay_multiple_interactions_nearest_is_target() -> void: "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], }, ] - var prompt = _make_prompt() - prompt._process(0.0) - # Target is entity 2 (nearest, Friendly) - assert_that(prompt.get_interaction_target()).is_equal(2) - var rel := prompt._get_entity_relationship(prompt.get_interaction_target()) - assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_FRIENDLY) - prompt.queue_free() + var list = _make_list() + list.update_from_state() + # interaction[0] = entity 2 (Friendly) → green bar + assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_FRIENDLY) + list.queue_free() + +func test_z_layer_is_insert_canvas() -> void: + # D-049 / D-057: interaction list lives on InsertOverlay (CanvasLayer 10) + var list = _make_list() + assert_that(list.get_z_layer()).is_equal(Constants.CANVAS_INSERT) + list.queue_free() # ------------------------------------------------------------------------- -# Scene-level: prompt in correct CanvasLayer +# Regression: interaction_list public API unaffected by #537 changes # ------------------------------------------------------------------------- -func test_interaction_prompt_in_insert_overlay() -> void: - # D-049 / D-057: InteractionPrompt must be in InsertOverlay (CanvasLayer 10) - var scene := load("res://scenes/main.tscn") - var instance = scene.instantiate() - auto_free(instance) - add_child(instance) +func test_list_hides_when_no_interactions() -> void: + GameState.nearby_interactions = [] + var list = _make_list() + list.update_from_state() + assert_that(list.is_showing()).is_false() + list.queue_free() - assert_that(instance.get_node_or_null("InsertOverlay/InteractionPrompt")).is_not_null() +func test_list_shows_on_npc_interaction() -> void: + _setup_npc_interaction(2, "Unknown") + var list = _make_list() + list.update_from_state() + assert_that(list.is_showing()).is_true() + list.queue_free() + +func test_list_get_selected_verb_returns_first_verb() -> void: + GameState.nearby_interactions = [{ + "entity_id": 2, "entity_type": "Npc", "distance": 1, + "verbs": [ + {"kind": "Talk", "label": "Talk", "priority": 1, "available": true}, + {"kind": "ExamineNpc", "label": "Look", "priority": 2, "available": true}, + ], + }] + var list = _make_list() + assert_that(list.get_selected_verb()).is_equal("Talk") + list.queue_free() + +func test_list_get_interaction_target_returns_entity_id() -> void: + GameState.visible_entities = [{ + "entity_id": 5, "x": 12.0, "y": 9.0, "z": 0, + "kind": {"variant": "Npc", "data": null}, + "visibility": "Forward", "relationship": "Unknown", "observation": "Visible", + }] + GameState.nearby_interactions = [{ + "entity_id": 5, "entity_type": "Npc", "distance": 1, + "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], + }] + var list = _make_list() + list.update_from_state() + assert_that(list.get_interaction_target()).is_equal(5) + list.queue_free() + +func test_list_suppressed_when_insert_inactive() -> void: + _setup_npc_interaction(2, "Unknown") + var list = _make_list() + list.set_insert_active(false) + list.update_from_state() + assert_that(list.is_showing()).is_false() + list.queue_free() + +func test_list_hides_on_empty_verb_list() -> void: + GameState.nearby_interactions = [{ + "entity_id": 2, "entity_type": "Npc", "distance": 1, "verbs": [], + }] + var list = _make_list() + list.update_from_state() + assert_that(list.is_showing()).is_false() + list.queue_free() + + +# ------------------------------------------------------------------------- +# Phase 2 placeholders — deferred pending server protocol change (no known_attributes in v13) +# ------------------------------------------------------------------------- + +func skip_test_npc_name_displayed_when_known() -> void: + pass + +func skip_test_dialogue_tier_context_hint_for_friendly() -> void: + pass + +func skip_test_dialogue_tier_context_hint_for_hostile() -> void: + pass # ------------------------------------------------------------------------- # Helpers # ------------------------------------------------------------------------- -func _make_prompt() -> PanelContainer: - var PromptScript = load("res://ui/interaction_prompt.gd") - var panel = PanelContainer.new() - panel.set_script(PromptScript) - var margin = MarginContainer.new() - margin.name = "MarginContainer" - panel.add_child(margin) - var vbox = VBoxContainer.new() - vbox.name = "VBoxContainer" - margin.add_child(vbox) - var prompt_label = Label.new() - prompt_label.name = "PromptLabel" - vbox.add_child(prompt_label) - var context_label = Label.new() - context_label.name = "ContextLabel" - vbox.add_child(context_label) - add_child(panel) # _ready() fires here: @onready vars resolve from the tree above - return panel +func _make_list() -> Control: + var scene = load("res://ui/interaction_list.tscn") + var list = scene.instantiate() + add_child(list) # _ready() fires here — @onready var _vbox resolves + return list + + +## Set up GameState with a single NPC entity + matching interaction for tests. +func _setup_npc_interaction(entity_id: int, relationship: String) -> void: + GameState.visible_entities = [{ + "entity_id": entity_id, "x": 12.0, "y": 9.0, "z": 0, + "kind": {"variant": "Npc", "data": null}, + "visibility": "Forward", "relationship": relationship, "observation": "Visible", + }] + GameState.nearby_interactions = [{ + "entity_id": entity_id, "entity_type": "Npc", "distance": 1, + "verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}], + }] diff --git a/client/tests/test_time_display_sprint17.gd b/client/tests/test_time_display_sprint17.gd index 5fd51ee05..ea38421dd 100644 --- a/client/tests/test_time_display_sprint17.gd +++ b/client/tests/test_time_display_sprint17.gd @@ -5,9 +5,9 @@ ## D-031 (game time: 10 ticks = 1 game-minute, 1440 min/day, HH:MM display) ## D-051 (diegetic insert display) ## -## Implementation: client/ui/insert_clock.gd — draw-based Control at UILayer/InsertClock. +## Implementation: client/ui/time_display.gd — draw-based Control at InsertOverlay/TimeDisplay. ## Format function: Constants.format_game_time(time_of_day: int) -> String (extracted for -## testability from insert_clock.gd:43 inline `"%02d:%02d" % [tod/60, tod%60]`). +## testability from time_display.gd:46 inline Constants.format_game_time(tod)). class_name TestTimeDisplaySprint17 extends GdUnitTestSuite @@ -17,7 +17,7 @@ var _clock: Control = null func before_test() -> void: SimBridge.reset_test_state() GameState.game_time = {} - var ClockScript = load("res://ui/insert_clock.gd") + var ClockScript = load("res://ui/time_display.gd") _clock = Control.new() _clock.set_script(ClockScript) add_child(_clock) @@ -260,7 +260,7 @@ func test_sim_bridge_day_phase_is_valid() -> void: # ------------------------------------------------------------------------- -# Scene: InsertClock node at UILayer/InsertClock +# Scene: InsertClock node at InsertOverlay/TimeDisplay # ------------------------------------------------------------------------- func test_insert_clock_exists_in_ui_layer() -> void: @@ -269,7 +269,7 @@ func test_insert_clock_exists_in_ui_layer() -> void: auto_free(instance) add_child(instance) - assert_that(instance.get_node_or_null("UILayer/InsertClock")).is_not_null() + assert_that(instance.get_node_or_null("InsertOverlay/TimeDisplay")).is_not_null() func test_insert_clock_time_str_updates_after_process() -> void: var scene := load("res://scenes/main.tscn") @@ -283,7 +283,7 @@ func test_insert_clock_time_str_updates_after_process() -> void: }) instance._process(0.016) - var clock = instance.get_node_or_null("UILayer/InsertClock") + var clock = instance.get_node_or_null("InsertOverlay/TimeDisplay") assert_that(clock).is_not_null() assert_that(clock._time_str).is_equal("12:00")