All checks were successful
Build Docker Image / build (pull_request) Successful in 1m23s
Add /evo status command showing paginated evolution progress: - Progress bar with formula value vs next threshold - Tier display names (Unranked/Initiate/Rising/Ascendant/Evolved) - Formula shorthands (PA+TB×2, IP+K) - Filters: card_type, tier, progress="close" (within 80%) - Pagination at 10 per page - Evolution cog registered in players_new/__init__.py - 15 unit tests for pure helper functions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
174 lines
5.7 KiB
Python
174 lines
5.7 KiB
Python
"""Tests for the evolution status command helpers (WP-11).
|
||
|
||
Unit tests for progress bar rendering, entry formatting, tier display
|
||
names, close-to-tierup filtering, and edge cases. No Discord bot or
|
||
API calls required — these test pure functions only.
|
||
"""
|
||
|
||
import pytest
|
||
from cogs.players_new.evolution import (
|
||
render_progress_bar,
|
||
format_evo_entry,
|
||
is_close_to_tierup,
|
||
TIER_NAMES,
|
||
FORMULA_SHORTHANDS,
|
||
)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# render_progress_bar
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestRenderProgressBar:
|
||
def test_80_percent_filled(self):
|
||
"""120/149 should be ~80% filled (8 of 10 chars)."""
|
||
result = render_progress_bar(120, 149, width=10)
|
||
assert "[========--]" in result
|
||
assert "120/149" in result
|
||
|
||
def test_zero_progress(self):
|
||
"""0/37 should be empty bar."""
|
||
result = render_progress_bar(0, 37, width=10)
|
||
assert "[----------]" in result
|
||
assert "0/37" in result
|
||
|
||
def test_full_progress_not_evolved(self):
|
||
"""Value at threshold shows full bar."""
|
||
result = render_progress_bar(149, 149, width=10)
|
||
assert "[==========]" in result
|
||
assert "149/149" in result
|
||
|
||
def test_fully_evolved(self):
|
||
"""next_threshold=None means fully evolved."""
|
||
result = render_progress_bar(900, None, width=10)
|
||
assert "FULLY EVOLVED" in result
|
||
assert "[==========]" in result
|
||
|
||
def test_over_threshold_capped(self):
|
||
"""Value exceeding threshold still caps at 100%."""
|
||
result = render_progress_bar(200, 149, width=10)
|
||
assert "[==========]" in result
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# format_evo_entry
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestFormatEvoEntry:
|
||
def test_batter_t1_to_t2(self):
|
||
"""Batter at T1 progressing toward T2."""
|
||
state = {
|
||
"current_tier": 1,
|
||
"current_value": 120.0,
|
||
"next_threshold": 149,
|
||
"fully_evolved": False,
|
||
"track": {"card_type": "batter"},
|
||
}
|
||
result = format_evo_entry(state)
|
||
assert "(PA+TB×2)" in result
|
||
assert "Initiate → Rising" in result
|
||
|
||
def test_pitcher_sp(self):
|
||
"""SP track shows IP+K formula."""
|
||
state = {
|
||
"current_tier": 0,
|
||
"current_value": 5.0,
|
||
"next_threshold": 10,
|
||
"fully_evolved": False,
|
||
"track": {"card_type": "sp"},
|
||
}
|
||
result = format_evo_entry(state)
|
||
assert "(IP+K)" in result
|
||
assert "Unranked → Initiate" in result
|
||
|
||
def test_fully_evolved_entry(self):
|
||
"""Fully evolved card shows T4 — Evolved."""
|
||
state = {
|
||
"current_tier": 4,
|
||
"current_value": 900.0,
|
||
"next_threshold": None,
|
||
"fully_evolved": True,
|
||
"track": {"card_type": "batter"},
|
||
}
|
||
result = format_evo_entry(state)
|
||
assert "FULLY EVOLVED" in result
|
||
assert "Evolved" in result
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# is_close_to_tierup
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestIsCloseToTierup:
|
||
def test_at_80_percent(self):
|
||
"""Exactly 80% of threshold counts as close."""
|
||
state = {"current_value": 119.2, "next_threshold": 149}
|
||
assert is_close_to_tierup(state, threshold_pct=0.80)
|
||
|
||
def test_below_80_percent(self):
|
||
"""Below 80% is not close."""
|
||
state = {"current_value": 100, "next_threshold": 149}
|
||
assert not is_close_to_tierup(state, threshold_pct=0.80)
|
||
|
||
def test_fully_evolved_not_close(self):
|
||
"""Fully evolved (no next threshold) is not close."""
|
||
state = {"current_value": 900, "next_threshold": None}
|
||
assert not is_close_to_tierup(state)
|
||
|
||
def test_zero_threshold(self):
|
||
"""Zero threshold edge case returns False."""
|
||
state = {"current_value": 0, "next_threshold": 0}
|
||
assert not is_close_to_tierup(state)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Tier names and formula shorthands
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestConstants:
|
||
def test_all_tier_names_present(self):
|
||
"""All 5 tiers (0-4) have display names."""
|
||
assert len(TIER_NAMES) == 5
|
||
for i in range(5):
|
||
assert i in TIER_NAMES
|
||
|
||
def test_tier_name_values(self):
|
||
assert TIER_NAMES[0] == "Unranked"
|
||
assert TIER_NAMES[1] == "Initiate"
|
||
assert TIER_NAMES[2] == "Rising"
|
||
assert TIER_NAMES[3] == "Ascendant"
|
||
assert TIER_NAMES[4] == "Evolved"
|
||
|
||
def test_formula_shorthands(self):
|
||
assert FORMULA_SHORTHANDS["batter"] == "PA+TB×2"
|
||
assert FORMULA_SHORTHANDS["sp"] == "IP+K"
|
||
assert FORMULA_SHORTHANDS["rp"] == "IP+K"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Empty / edge cases
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestEdgeCases:
|
||
def test_missing_track_defaults(self):
|
||
"""State with missing track info still formats without error."""
|
||
state = {
|
||
"current_tier": 0,
|
||
"current_value": 0,
|
||
"next_threshold": 37,
|
||
"fully_evolved": False,
|
||
"track": {},
|
||
}
|
||
result = format_evo_entry(state)
|
||
assert isinstance(result, str)
|
||
|
||
def test_state_with_no_keys(self):
|
||
"""Completely empty state dict doesn't crash."""
|
||
state = {}
|
||
result = format_evo_entry(state)
|
||
assert isinstance(result, str)
|