"""Tests for prompt rendering system.""" from mudlib.caps import ClientCaps from mudlib.combat.encounter import CombatEncounter from mudlib.combat.engine import active_encounters from mudlib.entity import Entity from mudlib.player import Player from mudlib.prompt import render_prompt def setup_function(): """Clear global state before each test.""" active_encounters.clear() def teardown_function(): """Clear global state after each test.""" active_encounters.clear() def test_normal_mode_prompt(): """Normal mode shows stamina gauge and power level.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # 50% is in 30-59% range, should be yellow assert result == "\033[33m<50%>\033[0m <200/100> " def test_combat_mode_with_opponent(): """Combat mode includes opponent name.""" player = Player( name="Goku", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "combat"], ) opponent = Entity(name="Vegeta", pl=150.0) encounter = CombatEncounter(attacker=player, defender=opponent) active_encounters.append(encounter) result = render_prompt(player) # 50% is in 30-59% range, should be yellow assert result == "\033[33m<50%>\033[0m <200/100> vs Vegeta > " def test_combat_mode_as_defender(): """Combat mode works when player is the defender.""" player = Player( name="Goku", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "combat"], ) opponent = Entity(name="Vegeta", pl=150.0) encounter = CombatEncounter(attacker=opponent, defender=player) active_encounters.append(encounter) result = render_prompt(player) # 50% is in 30-59% range, should be yellow assert result == "\033[33m<50%>\033[0m <200/100> vs Vegeta > " def test_editor_mode_static_prompt(): """Editor mode returns static prompt.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "editor"], ) result = render_prompt(player) assert result == "editor> " def test_if_mode_static_prompt(): """IF mode returns minimal static prompt.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "if"], ) result = render_prompt(player) assert result == "> " def test_zero_stamina(): """Zero stamina renders as 0% in red.""" player = Player( name="Test", stamina=0.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # 0% is < 30%, should be red assert result == "\033[31m<0%>\033[0m <200/100> " def test_max_stamina(): """Full stamina renders as 100% in green.""" player = Player( name="Test", stamina=100.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # 100% is >= 60%, should be green assert result == "\033[32m<100%>\033[0m <200/100> " def test_fractional_percentage(): """Stamina percentage rounds to integer.""" player = Player( name="Test", stamina=33.3, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # 33% is in 30-59% range, should be yellow assert result == "\033[33m<33%>\033[0m <200/100> " def test_fractional_pl(): """Power level rounds to integer.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=199.7, mode_stack=["normal"], ) result = render_prompt(player) # 50% is in 30-59% range, should be yellow assert result == "\033[33m<50%>\033[0m <200/100> " def test_custom_template_overrides_default(): """Player can set custom prompt template.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], prompt_template="[{pl}] > ", ) result = render_prompt(player) assert result == "[200] > " def test_custom_template_all_variables(): """Custom template can use all supported variables.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, max_pl=250.0, mode_stack=["normal"], prompt_template="{stamina}/{stamina_pct}% {pl}/{max_pl} > ", ) result = render_prompt(player) # No color tags in this custom template, so no ANSI codes assert result == "50/50% 200/250 > " def test_unknown_variable_left_as_is(): """Unknown variables in template are not substituted.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], prompt_template="{pl} {unknown} > ", ) result = render_prompt(player) assert result == "200 {unknown} > " def test_opponent_var_in_normal_mode_empty(): """Opponent variable is empty string when not in combat.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], prompt_template="{pl} vs {opponent} > ", ) result = render_prompt(player) assert result == "200 vs > " def test_no_color_support(): """Players with no ANSI support get tags stripped.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], caps=ClientCaps(ansi=False), ) result = render_prompt(player) # Should have no ANSI codes, just text assert result == "<50%> <200/100> " assert "\033[" not in result def test_stamina_gauge_green(): """Stamina >= 60% renders green gauge.""" player = Player( name="Test", stamina=80.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # Should have green color code assert "\033[32m<80%>\033[0m" in result def test_stamina_gauge_yellow(): """Stamina 30-59% renders yellow gauge.""" player = Player( name="Test", stamina=45.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # Should have yellow color code assert "\033[33m<45%>\033[0m" in result def test_stamina_gauge_red(): """Stamina < 30% renders red gauge.""" player = Player( name="Test", stamina=20.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # Should have red color code assert "\033[31m<20%>\033[0m" in result def test_stamina_gauge_boundary_60(): """Stamina exactly 60% renders green.""" player = Player( name="Test", stamina=60.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # Should have green color code assert "\033[32m<60%>\033[0m" in result def test_stamina_gauge_boundary_30(): """Stamina exactly 30% renders yellow.""" player = Player( name="Test", stamina=30.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # Should have yellow color code assert "\033[33m<30%>\033[0m" in result def test_stamina_gauge_boundary_29(): """Stamina at 29% renders red.""" player = Player( name="Test", stamina=29.5, max_stamina=100.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) # Should have red color code assert "\033[31m<29%>\033[0m" in result def test_zero_max_stamina(): """Zero max_stamina doesn't crash.""" player = Player( name="Test", stamina=0.0, max_stamina=0.0, pl=200.0, mode_stack=["normal"], ) result = render_prompt(player) assert "0%" in result def test_name_variable(): """Name variable substitutes player name.""" player = Player( name="Goku", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], prompt_template="{name} > ", ) result = render_prompt(player) assert result == "Goku > " def test_mode_variable(): """Mode variable substitutes current mode.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "combat"], prompt_template="[{mode}] > ", ) result = render_prompt(player) assert result == "[combat] > " def test_coordinates_variables(): """X and Y variables substitute coordinates.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], x=42, y=13, prompt_template="({x},{y}) > ", ) result = render_prompt(player) assert result == "(42,13) > " def test_combat_state_idle(): """Combat state is 'idle' when not in combat.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], prompt_template="[{combat_state}] > ", ) result = render_prompt(player) assert result == "[idle] > " def test_move_shows_name_when_in_combat_with_active_move(): """Move variable shows attack name when in combat with current_move.""" from mudlib.combat.moves import CombatMove player = Player( name="Goku", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "combat"], prompt_template="{move} > ", ) opponent = Entity(name="Vegeta", pl=150.0) punch = CombatMove( name="punch right", move_type="attack", stamina_cost=5.0, timing_window_ms=800, damage_pct=0.15, countered_by=["dodge left"], ) encounter = CombatEncounter(attacker=player, defender=opponent) encounter.attack(punch) active_encounters.append(encounter) result = render_prompt(player) assert result == "punch right > " def test_move_empty_when_not_in_combat(): """Move variable is empty string when not in combat.""" player = Player( name="Test", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal"], prompt_template="{move} > ", ) result = render_prompt(player) assert result == " > " def test_move_empty_when_in_combat_but_no_current_move(): """Move variable is empty when in combat but IDLE state (no current_move).""" player = Player( name="Goku", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "combat"], prompt_template="{move} > ", ) opponent = Entity(name="Vegeta", pl=150.0) encounter = CombatEncounter(attacker=player, defender=opponent) active_encounters.append(encounter) result = render_prompt(player) assert result == " > " def test_combat_state_shows_state_when_in_combat(): """Combat state variable shows encounter state value.""" from mudlib.combat.moves import CombatMove player = Player( name="Goku", stamina=50.0, max_stamina=100.0, pl=200.0, mode_stack=["normal", "combat"], prompt_template="[{combat_state}] > ", ) opponent = Entity(name="Vegeta", pl=150.0) punch = CombatMove( name="punch right", move_type="attack", stamina_cost=5.0, timing_window_ms=800, damage_pct=0.15, countered_by=["dodge left"], ) encounter = CombatEncounter(attacker=player, defender=opponent) encounter.attack(punch) active_encounters.append(encounter) result = render_prompt(player) assert result == "[telegraph] > "