GDScript 128 lines
extends GutTest
## Behavioural checks on the in-match HUD — the bottom cluster that surfaces the player's
## own hero (name, form, HP/resource, the QWER cooldown bar). They verify it binds to the
## live entity, hides when there is no hero to show, reads the per-form ability bar, and
## reflects the three cast states (ready, on cooldown, blocked on resource). The HUD owns no
## simulation, so this entity-in / readout-out behaviour is the whole of its logic; the
## floating world bars and the camera are the driver's job, not these unit tests.
const LION_HUMAN_Q := 10 # Sunfire Lash — the Lion's human slot-0 ability id
# Builds the HUD in the scene tree so `_ready` lays out its cluster, and frees it at test end.
func _hud() -> MatchHud:
var hud := MatchHud.new()
add_child_autoqfree(hud)
return hud
# A living, kitted hero straight from the sim: equips the kit so the resource pool, the
# per-form ability bar, and the starting form are all set exactly as a real match seats them.
func _hero(kit: String = "lion") -> SimEntity:
var sim := SimCore.new()
var id := sim.add_hero(0, Vector2.ZERO, 200.0)
sim.equip_kit(id, kit)
return sim.state.get_entity(id)
func test_hides_with_no_hero() -> void:
var hud := _hud()
hud.refresh(null)
assert_false(hud.visible, "the HUD hides when there is no hero to show")
func test_hides_for_a_dead_hero() -> void:
var hud := _hud()
var hero := _hero()
hero.respawn_ticks = 120 # down and counting to respawn — the death screen owns the screen
hud.refresh(hero)
assert_false(hud.visible, "the HUD hides while the hero is down")
func test_shows_for_a_living_hero() -> void:
var hud := _hud()
hud.refresh(_hero())
assert_true(hud.visible, "the HUD shows for a living hero")
func test_settings_button_emits_its_signal() -> void:
var hud := _hud()
watch_signals(hud)
hud._settings_button.pressed.emit()
assert_signal_emitted(hud, "settings_pressed", "the settings button re-emits as settings_pressed")
func test_bottom_cluster_lays_out_on_screen() -> void:
# Guards the layout regression where the bottom frame, pinned by hand-set anchors on a
# Container, collapsed to zero size and the whole HUD bar vanished. Container-pinned now,
# so after a layout pass the frame has a real on-screen size.
var hud := _hud()
hud.refresh(_hero())
await get_tree().process_frame
await get_tree().process_frame
assert_gt(hud._frame.size.x, 0.0, "the ability cluster lays out to a real width")
assert_gt(hud._frame.size.y, 0.0, "the ability cluster lays out to a real height")
func test_binds_name_and_form() -> void:
var hud := _hud()
hud.refresh(_hero("lion"))
assert_eq(hud._name_label.text, "Lion", "the portrait names the hero's kit")
assert_eq(hud._form_label.text, "HUMAN", "a hero starts in human form")
func test_hp_and_resource_fill_full() -> void:
var hud := _hud()
hud.refresh(_hero())
assert_eq(hud._hp["frac"], 1.0, "a fresh hero is at full HP")
assert_eq(hud._resource["frac"], 1.0, "a kitted hero starts with a full pool")
func test_resource_fraction_tracks_the_pool() -> void:
var hud := _hud()
var hero := _hero()
hero.resource = hero.resource_max / 2
hud.refresh(hero)
assert_almost_eq(hud._resource["frac"], 0.5, 0.02, "the resource bar tracks the pool")
func test_human_slots_show_the_kit_abilities() -> void:
var hud := _hud()
hud.refresh(_hero("lion"))
# Lion human form fills Q (Sunfire Lash), W (Mane Guard), R (Lion Form); E is the
# animal form's slot and reads empty here.
assert_eq(hud._cells[0]["name"].text, "Sunfire Lash", "Q carries the human slot-0 ability")
assert_eq(hud._cells[3]["name"].text, "Lion Form", "R carries the transform")
assert_eq(hud._cells[2]["name"].text, "", "E is the other form's slot — empty in human form")
func test_ready_ability_wears_the_ready_border() -> void:
var hud := _hud()
hud.refresh(_hero("lion"))
var style: StyleBoxFlat = hud._cells[0]["style"]
assert_eq(style.border_color, MatchHud.READY_BORDER, "ready when off cooldown and affordable")
func test_cooldown_shows_seconds_and_drains() -> void:
var hud := _hud()
var hero := _hero("lion")
hero.ability_cooldowns[LION_HUMAN_Q] = SimCore.TICK_RATE # one second of cooldown left
hud.refresh(hero)
var cell: Dictionary = hud._cells[0]
assert_true((cell["wash"] as ColorRect).visible, "the cooldown wash shows while recharging")
assert_eq((cell["cooldown"] as Label).text, "1", "the readout rounds the cooldown to seconds")
assert_gt(cell["frac"], 0.0, "the wash fraction tracks the remaining cooldown")
func test_unaffordable_ability_flags_the_resource() -> void:
var hud := _hud()
var hero := _hero("lion")
hero.resource = 0 # off cooldown but the pool is empty
hud.refresh(hero)
var style: StyleBoxFlat = hud._cells[0]["style"]
assert_eq(
style.border_color,
MatchHud.NO_RESOURCE_BORDER,
"an ability blocked only on resource flags the pool, not the timer",
)