ajhahn.de
← Theria
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",
	)