Fix 354 gdlint warnings across 65 files: 194 class-definitions-order (reorder declarations), 138 max-line-length (split long lines), 22 code issues (unused args, no-else-return, naming). Update .gdlintrc to exclude addons/ and raise max-public-methods for test files. No logic changes — declaration order, whitespace, and naming only. Ticket: #783 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
614 lines
22 KiB
GDScript
614 lines
22 KiB
GDScript
## Anti-tedium regression tests (#494):
|
|
## Guard against UI clutter that makes testing tedious.
|
|
##
|
|
## Test 1: F12 bug report capture — pressing F12 must not crash; when #495 lands,
|
|
## the handler should pause, show capture dialog, save 3 files, unpause.
|
|
## Test 2: Gauntlet progress UI hidden — in non-Gauntlet mode (no room_id or
|
|
## gauntlet_mode flag in snapshot), timer and personal-bests must not appear.
|
|
##
|
|
## #495 blocked by #481/#490 (server). #496 blocked by #487 (server).
|
|
## Tests stub blocked features and verify anti-tedium guards.
|
|
## Spec ref: Sprint 9 briefing (client.md), #495, #496.
|
|
class_name TestAntiTedium
|
|
extends GdUnitTestSuite
|
|
|
|
const MAIN_SCENE = preload("res://scenes/main.tscn")
|
|
|
|
var GauntletHUDScript = load("res://ui/gauntlet_hud.gd")
|
|
var BugReportDialogScript = load("res://ui/bug_report_dialog.gd")
|
|
var _instance: Node = null
|
|
|
|
|
|
func before_test() -> void:
|
|
SimBridge.reset_test_state()
|
|
SimBridge._last_snapshot = null
|
|
GameState.current_tick = 0
|
|
GameState.player_position = Vector2.ZERO
|
|
GameState.visible_entities = []
|
|
GameState.visible_tiles = []
|
|
GameState.visible_positions = {}
|
|
GameState.current_monologue = null
|
|
GameState.current_dialogue = null
|
|
GameState.game_time = {}
|
|
GameState.pending_recognitions = []
|
|
GameState.room_id = null
|
|
GameState.gauntlet_mode = false
|
|
|
|
|
|
func after_test() -> void:
|
|
if _instance and is_instance_valid(_instance):
|
|
_instance.queue_free()
|
|
_instance = null
|
|
|
|
|
|
# -- Helpers -------------------------------------------------------------------
|
|
|
|
## Encode a minimal valid snapshot as MessagePack bytes (same helper as P0 tests).
|
|
func _make_snapshot_bytes(overrides: Dictionary = {}) -> PackedByteArray:
|
|
var snapshot := {
|
|
"tick": overrides.get("tick", 1),
|
|
"version": Protocol.PROTOCOL_VERSION,
|
|
"entities": overrides.get("entities", [{
|
|
"entity_id": 1,
|
|
"x": 10.0,
|
|
"y": 10.0,
|
|
"z": 0,
|
|
"kind": "Player",
|
|
"visibility": "Forward",
|
|
}]),
|
|
"game_time": {
|
|
"day": 0,
|
|
"time_of_day": 0,
|
|
"day_phase": "Morning",
|
|
"tick_rate": overrides.get("tick_rate", "Full"),
|
|
},
|
|
"player_facing": "North",
|
|
"player_stance": "Walk",
|
|
"player_inventory": [],
|
|
"visible_tiles": [],
|
|
"nearby_interactions": [],
|
|
"pending_recognitions": [],
|
|
}
|
|
if overrides.has("current_monologue"):
|
|
snapshot["current_monologue"] = overrides["current_monologue"]
|
|
if overrides.has("current_dialogue"):
|
|
snapshot["current_dialogue"] = overrides["current_dialogue"]
|
|
# Gauntlet fields: only include if explicitly provided (absence = non-gauntlet)
|
|
if overrides.has("room_id"):
|
|
snapshot["room_id"] = overrides["room_id"]
|
|
if overrides.has("gauntlet_mode"):
|
|
snapshot["gauntlet_mode"] = overrides["gauntlet_mode"]
|
|
var result = Messagepack.encode(snapshot)
|
|
return result.value
|
|
|
|
|
|
func _make_gauntlet_hud() -> Control:
|
|
var hud = Control.new()
|
|
hud.set_script(GauntletHUDScript)
|
|
auto_free(hud)
|
|
add_child(hud)
|
|
hud._personal_bests = {} # Clear any stats loaded from disk
|
|
return hud
|
|
|
|
|
|
func _make_bug_report_dialog() -> Control:
|
|
var dialog = Control.new()
|
|
dialog.set_script(BugReportDialogScript)
|
|
auto_free(dialog)
|
|
add_child(dialog)
|
|
return dialog
|
|
|
|
|
|
# -- Test 1: F12 Bug Report Capture -------------------------------------------
|
|
# #495: BUG_REPORT action triggers the WRONG button capture flow.
|
|
# Verifies: dialog exists, activates on action, no state corruption.
|
|
# Note: Input.parse_input_event + _unhandled_input is unreliable in headless
|
|
# test mode. Tests drive the input queue directly for deterministic coverage.
|
|
|
|
func test_bug_report_dialog_exists_in_scene() -> void:
|
|
# Verify the BugReportDialog node is present and hidden by default.
|
|
var scene := MAIN_SCENE
|
|
_instance = scene.instantiate()
|
|
auto_free(_instance)
|
|
add_child(_instance)
|
|
_instance._process(0.016)
|
|
|
|
var dialog: Node = _find_node_recursive(_instance, "BugReportDialog")
|
|
assert_that(dialog).override_failure_message(
|
|
"BugReportDialog node should exist in scene tree"
|
|
).is_not_null()
|
|
if dialog is CanvasItem:
|
|
assert_that((dialog as CanvasItem).visible).override_failure_message(
|
|
"BugReportDialog should be hidden by default"
|
|
).is_false()
|
|
|
|
|
|
func test_bug_report_activates_on_action() -> void:
|
|
# Inject BUG_REPORT action directly into the queue and verify main.gd
|
|
# triggers the dialog. This tests the full main._process() handling path.
|
|
var scene := MAIN_SCENE
|
|
_instance = scene.instantiate()
|
|
auto_free(_instance)
|
|
add_child(_instance)
|
|
_instance._process(0.016)
|
|
|
|
# Record baseline state
|
|
var mono_before: Variant = GameState.current_monologue
|
|
var dialogue_before: Variant = GameState.current_dialogue
|
|
|
|
# Inject BUG_REPORT action into InputMapper queue
|
|
InputMapper.input_queue.append({
|
|
"action": InputMapper.Action.BUG_REPORT,
|
|
"timestamp_msec": Time.get_ticks_msec(),
|
|
})
|
|
|
|
# Process a frame — main.gd flushes the queue and triggers dialog
|
|
_instance._process(0.016)
|
|
|
|
# Assert: dialog should be active
|
|
var dialog: Node = _find_node_recursive(_instance, "BugReportDialog")
|
|
assert_that(dialog).is_not_null()
|
|
if dialog and dialog.has_method("is_active"):
|
|
assert_that(dialog.is_active()).override_failure_message(
|
|
"BugReportDialog should be active after BUG_REPORT action"
|
|
).is_true()
|
|
|
|
# Assert: no unintended state corruption
|
|
assert_that(GameState.current_monologue).override_failure_message(
|
|
"BUG_REPORT should not spawn a monologue"
|
|
).is_equal(mono_before)
|
|
assert_that(GameState.current_dialogue).override_failure_message(
|
|
"BUG_REPORT should not spawn a dialogue"
|
|
).is_equal(dialogue_before)
|
|
|
|
|
|
# -- Test 2: Gauntlet Progress UI Hidden in Non-Gauntlet Mode -----------------
|
|
# When #496 lands, it adds a room timer and personal-bests overlay.
|
|
# Anti-tedium guard: these must NOT be visible in normal (non-Gauntlet) play.
|
|
#
|
|
# Current state: no gauntlet UI exists in the scene tree.
|
|
# This test guards against #496 accidentally showing gauntlet UI in all modes.
|
|
|
|
func test_no_gauntlet_ui_visible_in_default_mode() -> void:
|
|
# Load main scene — represents non-Gauntlet (default) play mode
|
|
var scene := MAIN_SCENE
|
|
_instance = scene.instantiate()
|
|
auto_free(_instance)
|
|
add_child(_instance)
|
|
|
|
# Process one frame to initialize all children
|
|
_instance._process(0.016)
|
|
|
|
# Check that no gauntlet-specific UI nodes are visible in the scene tree.
|
|
# When #496 adds GauntletHUD/RoomTimer/PersonalBests, they must be hidden
|
|
# by default (only shown when gauntlet_mode is true in the snapshot).
|
|
var gauntlet_node_names := [
|
|
"GauntletHUD", "RoomTimer", "PersonalBests", "GauntletOverlay",
|
|
"GauntletTimer", "GauntletProgress",
|
|
]
|
|
for node_name in gauntlet_node_names:
|
|
var node: Node = _find_node_recursive(_instance, node_name)
|
|
if node != null and node is CanvasItem:
|
|
assert_that((node as CanvasItem).visible).override_failure_message(
|
|
"Gauntlet UI node '%s' must not be visible in non-Gauntlet mode" % node_name
|
|
).is_false()
|
|
|
|
|
|
func test_snapshot_without_room_id_shows_no_gauntlet_ui() -> void:
|
|
# A snapshot that lacks room_id and gauntlet_mode fields represents
|
|
# normal play. Apply it and verify no gauntlet state leaks into GameState.
|
|
var bytes := _make_snapshot_bytes({"tick": 1})
|
|
SimBridge.receive_bytes(bytes)
|
|
|
|
var snapshot: Variant = SimBridge._last_snapshot
|
|
assert_that(snapshot).is_not_null()
|
|
|
|
# Snapshot should NOT contain gauntlet fields
|
|
assert_that(snapshot.has("room_id")).override_failure_message(
|
|
"Non-gauntlet snapshot must not contain room_id"
|
|
).is_false()
|
|
assert_that(snapshot.has("gauntlet_mode")).override_failure_message(
|
|
"Non-gauntlet snapshot must not contain gauntlet_mode"
|
|
).is_false()
|
|
|
|
# Apply to GameState — gauntlet-related state should not exist
|
|
GameState.apply_snapshot(snapshot)
|
|
# #496: GameState now has room_id (Variant, null) and gauntlet_mode (bool, false).
|
|
# A non-gauntlet snapshot must leave these at their defaults.
|
|
assert_that(GameState.room_id).override_failure_message(
|
|
"GameState.room_id should be null in non-gauntlet mode"
|
|
).is_null()
|
|
assert_that(GameState.gauntlet_mode).override_failure_message(
|
|
"GameState.gauntlet_mode should be false in non-gauntlet mode"
|
|
).is_false()
|
|
|
|
|
|
func test_gauntlet_ui_stays_hidden_after_multiple_snapshots() -> void:
|
|
# Simulate several ticks of normal play — gauntlet UI must never appear.
|
|
var scene := MAIN_SCENE
|
|
_instance = scene.instantiate()
|
|
auto_free(_instance)
|
|
add_child(_instance)
|
|
|
|
# Process 5 frames with normal (non-gauntlet) snapshots
|
|
for tick in range(1, 6):
|
|
var bytes := _make_snapshot_bytes({"tick": tick})
|
|
SimBridge.receive_bytes(bytes)
|
|
_instance._process(0.016)
|
|
|
|
# After 5 frames, no gauntlet nodes should have appeared
|
|
var gauntlet_node_names := [
|
|
"GauntletHUD", "RoomTimer", "PersonalBests", "GauntletOverlay",
|
|
"GauntletTimer", "GauntletProgress",
|
|
]
|
|
for node_name in gauntlet_node_names:
|
|
var node: Node = _find_node_recursive(_instance, node_name)
|
|
if node != null and node is CanvasItem:
|
|
assert_that((node as CanvasItem).visible).override_failure_message(
|
|
"Gauntlet UI '%s' must stay hidden after %d non-gauntlet ticks" % [node_name, 5]
|
|
).is_false()
|
|
|
|
|
|
# -- GauntletHUD Feature Tests (#496) ----------------------------------------
|
|
# Timer lifecycle, personal bests, room change, visibility.
|
|
# Spec: sprint-9/client.md #496. Ticket: tooling/db/ticket show 496.
|
|
|
|
func test_format_time_zero() -> void:
|
|
# Static utility: 0 seconds → "00:00"
|
|
var hud := _make_gauntlet_hud()
|
|
assert_that(hud._format_time(0.0)).is_equal("00:00")
|
|
|
|
|
|
func test_format_time_sub_minute() -> void:
|
|
# 47.9 seconds (truncates, no rounding) → "00:47"
|
|
var hud := _make_gauntlet_hud()
|
|
assert_that(hud._format_time(47.9)).is_equal("00:47")
|
|
|
|
|
|
func test_format_time_over_minute() -> void:
|
|
# 98.3 seconds → 1 min 38 sec → "01:38"
|
|
var hud := _make_gauntlet_hud()
|
|
assert_that(hud._format_time(98.3)).is_equal("01:38")
|
|
|
|
|
|
func test_gauntlet_hud_shows_when_gauntlet_mode_active() -> void:
|
|
# HUD must become visible when gauntlet_mode is true in the snapshot.
|
|
var hud := _make_gauntlet_hud()
|
|
assert_that(hud.visible).is_false()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
assert_that(hud.visible).override_failure_message(
|
|
"GauntletHUD must be visible when gauntlet_mode is true"
|
|
).is_true()
|
|
|
|
|
|
func test_gauntlet_hud_hides_when_gauntlet_mode_deactivates() -> void:
|
|
# HUD must hide when gauntlet_mode transitions from true to false.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
assert_that(hud.visible).is_true()
|
|
# Switch to non-gauntlet
|
|
GameState.gauntlet_mode = false
|
|
GameState.room_id = null
|
|
hud.update_from_state()
|
|
assert_that(hud.visible).override_failure_message(
|
|
"GauntletHUD must hide when gauntlet_mode becomes false"
|
|
).is_false()
|
|
|
|
|
|
func test_gauntlet_hud_timer_starts_on_room_entry() -> void:
|
|
# Timer starts running and tracks room_id after room entry.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
assert_that(hud.is_timer_running()).override_failure_message(
|
|
"Timer should be running after room entry"
|
|
).is_true()
|
|
assert_that(hud.get_current_room_id()).is_equal("room_1")
|
|
|
|
|
|
func test_gauntlet_hud_timer_increments_with_delta() -> void:
|
|
# Timer accumulates real time via _process(delta).
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(0.5)
|
|
assert_that(hud.get_timer_seconds()).override_failure_message(
|
|
"Timer should increment by delta (0.5s)"
|
|
).is_equal_approx(0.5, 0.01)
|
|
hud._process(1.0)
|
|
assert_that(hud.get_timer_seconds()).override_failure_message(
|
|
"Timer should accumulate (0.5 + 1.0 = 1.5s)"
|
|
).is_equal_approx(1.5, 0.01)
|
|
|
|
|
|
func test_gauntlet_hud_timer_resets_on_room_change() -> void:
|
|
# Changing room_id resets the timer to 0 and starts counting for the new room.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(5.0)
|
|
assert_that(hud.get_timer_seconds() > 4.0).is_true()
|
|
# Change room
|
|
GameState.room_id = "room_2"
|
|
hud.update_from_state()
|
|
assert_that(hud.get_timer_seconds()).override_failure_message(
|
|
"Timer should reset to 0 on room change"
|
|
).is_equal_approx(0.0, 0.01)
|
|
assert_that(hud.get_current_room_id()).is_equal("room_2")
|
|
assert_that(hud.is_timer_running()).is_true()
|
|
|
|
|
|
func test_gauntlet_hud_records_personal_best() -> void:
|
|
# Completing a room (by entering a new room) records a personal best.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(10.0)
|
|
# Entering room_2 triggers _record_room_completion for room_1
|
|
GameState.room_id = "room_2"
|
|
hud.update_from_state()
|
|
assert_that(hud.get_personal_best("room_1")).override_failure_message(
|
|
"PB for room_1 should be ~10.0s after first completion"
|
|
).is_equal_approx(10.0, 0.1)
|
|
|
|
|
|
func test_gauntlet_hud_non_pb_does_not_overwrite() -> void:
|
|
# A worse time must not overwrite an existing personal best.
|
|
var hud := _make_gauntlet_hud()
|
|
hud._personal_bests["room_1"] = 5.0 # Existing PB of 5 seconds
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(10.0) # Worse time than existing PB
|
|
GameState.room_id = "room_2"
|
|
hud.update_from_state()
|
|
assert_that(hud.get_personal_best("room_1")).override_failure_message(
|
|
"PB should remain 5.0 when new time (10.0) is worse"
|
|
).is_equal_approx(5.0, 0.01)
|
|
|
|
|
|
func test_gauntlet_hud_pb_overwrites_when_faster() -> void:
|
|
# A faster time must overwrite an existing personal best.
|
|
var hud := _make_gauntlet_hud()
|
|
hud._personal_bests["room_1"] = 15.0 # Existing PB of 15 seconds
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(8.0) # Better time
|
|
GameState.room_id = "room_2"
|
|
hud.update_from_state()
|
|
assert_that(hud.get_personal_best("room_1")).override_failure_message(
|
|
"PB should update to 8.0 when new time beats old PB (15.0)"
|
|
).is_equal_approx(8.0, 0.1)
|
|
|
|
|
|
func test_gauntlet_hud_null_room_in_gauntlet_mode() -> void:
|
|
# Gauntlet mode active but no room_id yet — HUD visible, timer stopped.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = null
|
|
hud.update_from_state()
|
|
assert_that(hud.visible).override_failure_message(
|
|
"HUD should be visible in gauntlet mode even without room_id"
|
|
).is_true()
|
|
assert_that(hud.is_timer_running()).override_failure_message(
|
|
"Timer should NOT run when room_id is null"
|
|
).is_false()
|
|
|
|
|
|
func test_gauntlet_hud_timer_paused_when_not_visible() -> void:
|
|
# Timer should not accumulate when HUD is not visible (non-gauntlet mode).
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(2.0)
|
|
assert_that(hud.get_timer_seconds()).is_equal_approx(2.0, 0.01)
|
|
# Switch to non-gauntlet (hides HUD)
|
|
GameState.gauntlet_mode = false
|
|
hud.update_from_state()
|
|
hud._process(5.0) # Should NOT accumulate
|
|
assert_that(hud.get_timer_seconds()).override_failure_message(
|
|
"Timer must not accumulate when HUD is hidden"
|
|
).is_equal_approx(2.0, 0.01)
|
|
|
|
|
|
func test_gauntlet_hud_finalize_records_current_room() -> void:
|
|
# finalize() records the current room's time and stops the timer.
|
|
# Called on disconnect via _on_connection_state_changed.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(7.5)
|
|
hud.finalize()
|
|
assert_that(hud.is_timer_running()).override_failure_message(
|
|
"Timer should stop after finalize"
|
|
).is_false()
|
|
assert_that(hud.get_personal_best("room_1")).override_failure_message(
|
|
"Finalize should record current room's time as PB"
|
|
).is_equal_approx(7.5, 0.1)
|
|
|
|
|
|
func test_gauntlet_hud_session_tracks_attempts() -> void:
|
|
# Each room entry increments the attempt counter in _session_rooms.
|
|
var hud := _make_gauntlet_hud()
|
|
GameState.gauntlet_mode = true
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
hud._process(3.0)
|
|
# Re-enter same room (via room change and back)
|
|
GameState.room_id = "room_2"
|
|
hud.update_from_state()
|
|
GameState.room_id = "room_1"
|
|
hud.update_from_state()
|
|
assert_that(hud._session_rooms.has("room_1")).is_true()
|
|
assert_that(hud._session_rooms["room_1"]["attempts"]).override_failure_message(
|
|
"Room should show 2 attempts after two entries"
|
|
).is_equal(2)
|
|
|
|
|
|
func test_gauntlet_snapshot_roundtrip_via_apply() -> void:
|
|
# Snapshot with gauntlet fields applied to GameState, consumed by HUD.
|
|
# End-to-end: bytes → decode → apply → update_from_state.
|
|
var bytes := _make_snapshot_bytes({
|
|
"tick": 10,
|
|
"gauntlet_mode": true,
|
|
"room_id": "warehouse_01",
|
|
})
|
|
SimBridge.receive_bytes(bytes)
|
|
GameState.apply_snapshot(SimBridge._last_snapshot)
|
|
assert_that(GameState.gauntlet_mode).is_true()
|
|
assert_that(GameState.room_id).is_equal("warehouse_01")
|
|
var hud := _make_gauntlet_hud()
|
|
hud.update_from_state()
|
|
assert_that(hud.visible).is_true()
|
|
assert_that(hud.get_current_room_id()).is_equal("warehouse_01")
|
|
assert_that(hud.is_timer_running()).is_true()
|
|
|
|
|
|
# -- BugReportDialog Feature Tests (#495) ------------------------------------
|
|
# Pause/unpause lifecycle, wire guard, text render, state machine.
|
|
# Spec: sprint-9/client.md #495. Ticket: tooling/db/ticket show 495.
|
|
|
|
func test_bug_report_sends_pause_on_open() -> void:
|
|
# start_capture() must send Pause to the server.
|
|
SimBridge.connect_to_sim()
|
|
SimBridge._test_input_queue.clear()
|
|
var dialog := _make_bug_report_dialog()
|
|
dialog.start_capture()
|
|
assert_that(dialog.is_active()).is_true()
|
|
assert_that(SimBridge._test_input_queue.has("Pause")).override_failure_message(
|
|
"Opening bug report should send Pause to server"
|
|
).is_true()
|
|
|
|
|
|
func test_bug_report_sends_unpause_on_close() -> void:
|
|
# _close() must send Unpause to the server.
|
|
SimBridge.connect_to_sim()
|
|
var dialog := _make_bug_report_dialog()
|
|
dialog.start_capture()
|
|
SimBridge._test_input_queue.clear()
|
|
dialog._close()
|
|
assert_that(dialog.is_active()).is_false()
|
|
assert_that(SimBridge._test_input_queue.has("Unpause")).override_failure_message(
|
|
"Closing bug report should send Unpause to server"
|
|
).is_true()
|
|
|
|
|
|
func test_bug_report_not_double_activatable() -> void:
|
|
# Calling start_capture() twice must not double-activate or send duplicate Pause.
|
|
SimBridge.connect_to_sim()
|
|
var dialog := _make_bug_report_dialog()
|
|
dialog.start_capture()
|
|
assert_that(dialog.is_active()).is_true()
|
|
SimBridge._test_input_queue.clear()
|
|
dialog.start_capture() # Second call — should be blocked
|
|
assert_that(SimBridge._test_input_queue.size()).override_failure_message(
|
|
"Double-activation should be blocked (no second Pause sent)"
|
|
).is_equal(0)
|
|
|
|
|
|
func test_bug_report_action_not_on_wire() -> void:
|
|
# BUG_REPORT is client-only — must not produce a wire-format action name.
|
|
var wire_name := SimBridge.action_enum_to_wire(InputMapper.Action.BUG_REPORT)
|
|
assert_that(wire_name).override_failure_message(
|
|
"BUG_REPORT must not produce a wire action name (client-only)"
|
|
).is_equal("")
|
|
|
|
|
|
func test_bug_report_render_text_contains_tick_and_entities() -> void:
|
|
# _render_snapshot_text() must include tick number and entity data from GameState.
|
|
GameState.current_tick = 42
|
|
GameState.player_position = Vector2(5.0, 7.0)
|
|
GameState.player_facing = "South"
|
|
GameState.player_stance = "Crouch"
|
|
GameState.visible_entities = [{
|
|
"entity_id": 1, "x": 5.0, "y": 7.0, "z": 0,
|
|
"kind": {"variant": "Player", "data": null}, "visibility": "Forward",
|
|
}]
|
|
GameState.visible_tiles = []
|
|
GameState.game_time = {"day": 1, "day_phase": "Evening", "tick_rate": "Full"}
|
|
GameState.current_monologue = null
|
|
GameState.current_dialogue = null
|
|
var dialog := _make_bug_report_dialog()
|
|
var text: String = dialog._render_snapshot_text()
|
|
assert_that(text.contains("t42")).override_failure_message(
|
|
"Render text should contain tick number"
|
|
).is_true()
|
|
assert_that(text.contains("Entities (1)")).override_failure_message(
|
|
"Render text should show entity count"
|
|
).is_true()
|
|
assert_that(text.contains("Player")).override_failure_message(
|
|
"Render text should contain entity kind"
|
|
).is_true()
|
|
assert_that(text.contains("South")).override_failure_message(
|
|
"Render text should contain player facing"
|
|
).is_true()
|
|
|
|
|
|
func test_bug_report_render_text_with_monologue_and_dialogue() -> void:
|
|
# _render_snapshot_text() should include active monologue and dialogue.
|
|
GameState.current_tick = 10
|
|
GameState.player_position = Vector2(3.0, 3.0)
|
|
GameState.player_facing = "North"
|
|
GameState.player_stance = "Walk"
|
|
GameState.visible_entities = []
|
|
GameState.visible_tiles = []
|
|
GameState.game_time = {"day": 0, "day_phase": "Morning", "tick_rate": "Full"}
|
|
GameState.current_monologue = {"text": "Something is wrong here."}
|
|
GameState.current_dialogue = {"npc_name": "Kael", "speech": "Who are you?"}
|
|
var dialog := _make_bug_report_dialog()
|
|
var text: String = dialog._render_snapshot_text()
|
|
assert_that(text.contains("Something is wrong here.")).override_failure_message(
|
|
"Render text should include monologue text"
|
|
).is_true()
|
|
assert_that(text.contains("Kael")).override_failure_message(
|
|
"Render text should include dialogue NPC name"
|
|
).is_true()
|
|
assert_that(text.contains("Who are you?")).override_failure_message(
|
|
"Render text should include dialogue speech"
|
|
).is_true()
|
|
|
|
|
|
func test_bug_report_render_text_empty_snapshot() -> void:
|
|
# Edge case: F12 pressed before any snapshot data arrives.
|
|
GameState.current_tick = 0
|
|
GameState.player_position = Vector2.ZERO
|
|
GameState.player_facing = "North"
|
|
GameState.player_stance = "Walk"
|
|
GameState.visible_entities = []
|
|
GameState.visible_tiles = []
|
|
GameState.game_time = {}
|
|
GameState.current_monologue = null
|
|
GameState.current_dialogue = null
|
|
var dialog := _make_bug_report_dialog()
|
|
var text: String = dialog._render_snapshot_text()
|
|
# Should not crash, should produce some output
|
|
assert_that(text.length() > 0).override_failure_message(
|
|
"Render text should not be empty even with no snapshot data"
|
|
).is_true()
|
|
assert_that(text.contains("Entities (0)")).override_failure_message(
|
|
"Empty snapshot should show 0 entities"
|
|
).is_true()
|
|
|
|
|
|
# -- Helper: recursive node search --------------------------------------------
|
|
|
|
func _find_node_recursive(root: Node, target_name: String) -> Node:
|
|
if root.name == target_name:
|
|
return root
|
|
for child in root.get_children():
|
|
var found := _find_node_recursive(child, target_name)
|
|
if found != null:
|
|
return found
|
|
return null
|