From bbad1daba2a47c0ed2d7fd496b4d40e474eb8971 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 26 Mar 2026 00:22:35 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20clean=20up=20refractor=20status=20displa?= =?UTF-8?q?y=20=E2=80=94=20suffix=20tags,=20compact=20layout,=20dead=20cod?= =?UTF-8?q?e=20removal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tier labels as suffix tags: **Name** — Base Chrome [T1] (T0 gets no suffix) - Compact progress line: bar value/threshold (pct) — removed formula and tier arrow - Fully evolved shows `MAX` instead of FULLY EVOLVED - Deleted unused FORMULA_LABELS dict - Added _FULL_BAR constant, moved T0-branch lookups into else - Fixed mock API shape in test (cards → items) Co-Authored-By: Claude Opus 4.6 (1M context) --- cogs/refractor.py | 61 ++++++-------- tests/test_refractor_commands.py | 139 +++++++++---------------------- 2 files changed, 63 insertions(+), 137 deletions(-) diff --git a/cogs/refractor.py b/cogs/refractor.py index 5cba435..9a90555 100644 --- a/cogs/refractor.py +++ b/cogs/refractor.py @@ -34,21 +34,17 @@ TIER_NAMES = { 4: "Superfractor", } -FORMULA_LABELS = { - "batter": "PA+TB×2", - "sp": "IP+K", - "rp": "IP+K", -} - -# Tier-specific labels for the status display. T0 is blank (base cards need no prefix). +# Tier-specific labels for the status display. TIER_SYMBOLS = { - 0: "", # Base Card — no prefix + 0: "Base", # Base Card — used in summary only, not in per-card display 1: "T1", # Base Chrome 2: "T2", # Refractor 3: "T3", # Gold Refractor - 4: "T4★", # Superfractor — star earned + 4: "T4★", # Superfractor } +_FULL_BAR = "▰" * 12 + # Embed accent colors per tier (used for single-tier filtered views). TIER_COLORS = { 0: 0x95A5A6, # slate grey @@ -86,40 +82,38 @@ def _pct_label(current: int, threshold: int) -> str: def format_refractor_entry(card_state: dict) -> str: """ - Format a single card state dict as a rich display string. + Format a single card state dict as a compact two-line display string. - Output example (in-progress): - ◈ **Mike Trout** — Base Chrome - ▰▰▰▰▰▰▰▰▰▰▱▱ `120/149` 80% · PA+TB×2 · T1 → T2 + Output example (base card — no suffix): + **Mike Trout** + ▰▰▰▰▰▰▰▰▰▰▱▱ 120/149 (80%) + + Output example (evolved — suffix tag): + **Mike Trout** — Base Chrome [T1] + ▰▰▰▰▰▰▰▰▰▰▱▱ 120/149 (80%) Output example (fully evolved): - ★ **Barry Bonds** — Superfractor - ▰▰▰▰▰▰▰▰▰▰▰▰ ✧ FULLY EVOLVED ✧ + **Barry Bonds** — Superfractor [T4★] + ▰▰▰▰▰▰▰▰▰▰▰▰ `MAX` """ player_name = card_state.get("player_name", "Unknown") - track = card_state.get("track", {}) - card_type = track.get("card_type", "batter") current_tier = card_state.get("current_tier", 0) formula_value = int(card_state.get("current_value", 0)) next_threshold = int(card_state.get("next_threshold") or 0) or None - tier_label = TIER_NAMES.get(current_tier, f"T{current_tier}") - formula_label = FORMULA_LABELS.get(card_type, card_type) - symbol = TIER_SYMBOLS.get(current_tier, "") - prefix = f"{symbol} " if symbol else "" - - first_line = f"{prefix}**{player_name}** — {tier_label}" + if current_tier == 0: + first_line = f"**{player_name}**" + else: + tier_label = TIER_NAMES.get(current_tier, f"T{current_tier}") + symbol = TIER_SYMBOLS.get(current_tier, "") + first_line = f"**{player_name}** — {tier_label} [{symbol}]" if current_tier >= 4 or next_threshold is None: - bar = render_progress_bar(1, 1) - second_line = f"{bar} ✧ FULLY EVOLVED ✧" + second_line = f"{_FULL_BAR} `MAX`" else: bar = render_progress_bar(formula_value, next_threshold) pct = _pct_label(formula_value, next_threshold) - second_line = ( - f"{bar} `{formula_value}/{next_threshold}` {pct}" - f" · {formula_label} · T{current_tier} → T{current_tier + 1}" - ) + second_line = f"{bar} {formula_value}/{next_threshold} ({pct})" return f"{first_line}\n{second_line}" @@ -139,8 +133,7 @@ def build_tier_summary(items: list, total_count: int) -> str: parts = [] for t in range(5): if counts[t] > 0: - label = TIER_SYMBOLS[t] or "Base" - parts.append(f"{label}: {counts[t]}") + parts.append(f"{TIER_SYMBOLS[t]}: {counts[t]}") summary = " ".join(parts) if parts else "No cards" return f"{summary} — {total_count} total" @@ -165,17 +158,11 @@ def build_status_embed( if tier_filter is not None and tier_filter in TIER_COLORS: embed.color = TIER_COLORS[tier_filter] - # Header: tier distribution summary header = build_tier_summary(items, total_count) - - # Card entries lines = [format_refractor_entry(state) for state in items] body = "\n\n".join(lines) if lines else "*No cards found.*" - - # Separator between header and cards embed.description = f"```{header}```\n{body}" - # Page indicator in footer (append to existing footer text) existing_footer = embed.footer.text or "" page_text = f"Page {page}/{total_pages}" embed.set_footer( diff --git a/tests/test_refractor_commands.py b/tests/test_refractor_commands.py index 7972ee1..0d7405e 100644 --- a/tests/test_refractor_commands.py +++ b/tests/test_refractor_commands.py @@ -153,37 +153,22 @@ class TestFormatRefractorEntry: result = format_refractor_entry(batter_state) assert "120/149" in result - def test_formula_label_batter(self, batter_state): - """Batter formula label PA+TB×2 appears in output.""" + def test_percentage_in_output(self, batter_state): + """Percentage appears in parentheses in output.""" result = format_refractor_entry(batter_state) - assert "PA+TB×2" in result - - def test_tier_progression_arrow(self, batter_state): - """T1 → T2 arrow progression appears for non-evolved cards.""" - result = format_refractor_entry(batter_state) - assert "T1 → T2" in result - - def test_sp_formula_label(self, sp_state): - """SP formula label IP+K appears for starting pitchers.""" - result = format_refractor_entry(sp_state) - assert "IP+K" in result + assert "(80%)" in result or "(81%)" in result def test_fully_evolved_no_threshold(self, evolved_state): - """T4 card with next_threshold=None shows FULLY EVOLVED.""" + """T4 card with next_threshold=None shows MAX.""" result = format_refractor_entry(evolved_state) - assert "FULLY EVOLVED" in result + assert "`MAX`" in result def test_fully_evolved_by_tier(self, batter_state): """current_tier=4 triggers fully evolved display even with a threshold.""" batter_state["current_tier"] = 4 batter_state["next_threshold"] = 200 result = format_refractor_entry(batter_state) - assert "FULLY EVOLVED" in result - - def test_fully_evolved_no_arrow(self, evolved_state): - """Fully evolved cards don't show a tier arrow.""" - result = format_refractor_entry(evolved_state) - assert "→" not in result + assert "`MAX`" in result def test_two_line_output(self, batter_state): """Output always has exactly two lines (name line + bar line).""" @@ -205,7 +190,7 @@ class TestTierSymbols: def test_t0_symbol(self): """T0 label is empty (base cards get no prefix).""" - assert TIER_SYMBOLS[0] == "" + assert TIER_SYMBOLS[0] == "Base" def test_t1_symbol(self): """T1 label is 'T1'.""" @@ -223,41 +208,40 @@ class TestTierSymbols: """T4 label is 'T4★'.""" assert TIER_SYMBOLS[4] == "T4★" - def test_format_entry_t1_label_present(self, batter_state): - """format_refractor_entry prepends T1 label for T1 cards.""" + def test_format_entry_t1_suffix_tag(self, batter_state): + """T1 cards show [T1] suffix tag after the tier name.""" result = format_refractor_entry(batter_state) - assert "T1 " in result + assert "[T1]" in result - def test_format_entry_t2_label_present(self, sp_state): - """format_refractor_entry prepends T2 label for T2 cards.""" + def test_format_entry_t2_suffix_tag(self, sp_state): + """T2 cards show [T2] suffix tag.""" result = format_refractor_entry(sp_state) - assert "T2 " in result + assert "[T2]" in result - def test_format_entry_t4_label_present(self, evolved_state): - """format_refractor_entry prepends T4★ label for T4 cards.""" + def test_format_entry_t4_suffix_tag(self, evolved_state): + """T4 cards show [T4★] suffix tag.""" result = format_refractor_entry(evolved_state) - assert "T4★" in result + assert "[T4★]" in result - def test_format_entry_t0_no_prefix(self): - """T0 cards have no tier prefix — name starts the line.""" + def test_format_entry_t0_name_only(self): + """T0 cards show just the bold name, no tier suffix.""" state = { "player_name": "Rookie Player", - "track": {"card_type": "batter"}, "current_tier": 0, "current_value": 10, "next_threshold": 50, } result = format_refractor_entry(state) first_line = result.split("\n")[0] - assert first_line.startswith("**Rookie Player**") + assert first_line == "**Rookie Player**" - def test_format_entry_label_before_name(self, batter_state): - """Label appears before the player name in the first line.""" + def test_format_entry_tag_after_name(self, batter_state): + """Tag appears after the player name in the first line.""" result = format_refractor_entry(batter_state) first_line = result.split("\n")[0] - label_pos = first_line.find("T1") name_pos = first_line.find("Mike Trout") - assert label_pos < name_pos + tag_pos = first_line.find("[T1]") + assert name_pos < tag_pos # --------------------------------------------------------------------------- @@ -589,11 +573,10 @@ class TestFormatRefractorEntryMalformedInput: lines = result.split("\n") assert len(lines) == 2 - def test_missing_card_type_uses_raw_fallback(self): + def test_missing_card_type_does_not_crash(self): """ - When card_type is absent from the track, the code defaults to 'batter' - internally (via .get("card_type", "batter")), so "PA+TB×2" should - appear as the formula label. + When card_type is absent from the track, the code should still + produce a valid two-line output without crashing. """ state = { "player_name": "Test Player", @@ -602,7 +585,7 @@ class TestFormatRefractorEntryMalformedInput: "next_threshold": 100, } result = format_refractor_entry(state) - assert "PA+TB×2" in result + assert "50/100" in result # --------------------------------------------------------------------------- @@ -670,22 +653,14 @@ class TestRenderProgressBarBoundaryPrecision: # --------------------------------------------------------------------------- -class TestRPFormulaLabel: +class TestCardTypeVariants: """ - T3-4: Verify that relief pitchers (card_type="rp") show the "IP+K" formula - label in format_refractor_entry output. - - Why: FORMULA_LABELS maps both "sp" and "rp" to "IP+K". The existing test - suite only verifies "sp" (via the sp_state fixture). Adding "rp" explicitly - prevents a future refactor from accidentally giving RPs a different label - or falling through to the raw card_type fallback. + T3-4/T3-5: Verify that format_refractor_entry produces valid output for + all card types including unknown ones, without crashing. """ - def test_rp_formula_label_is_ip_plus_k(self): - """ - A card with card_type="rp" must show "IP+K" as the formula label - in its progress line. - """ + def test_rp_card_produces_valid_output(self): + """Relief pitcher card produces a valid two-line string.""" rp_state = { "player_name": "Edwin Diaz", "track": {"card_type": "rp"}, @@ -694,50 +669,11 @@ class TestRPFormulaLabel: "next_threshold": 60, } result = format_refractor_entry(rp_state) - assert "IP+K" in result, ( - f"Relief pitcher card should show 'IP+K' formula label, got: {result!r}" - ) - - -# --------------------------------------------------------------------------- -# T3-5: Unknown card_type fallback -# --------------------------------------------------------------------------- - - -class TestUnknownCardTypeFallback: - """ - T3-5: format_refractor_entry should use the raw card_type string as the - formula label when the type is not in FORMULA_LABELS, rather than crashing. - - Why: FORMULA_LABELS only covers "batter", "sp", and "rp". If the API - introduces a new card type (e.g. "util" for utility players) before the - bot is updated, FORMULA_LABELS.get(card_type, card_type) will fall back to - the raw string. This test ensures that fallback path produces readable - output rather than an error, and explicitly documents what to expect. - """ - - def test_unknown_card_type_uses_raw_string_as_label(self): - """ - card_type="util" is not in FORMULA_LABELS. The output should include - "util" as the formula label (the raw fallback) and must not raise. - """ - util_state = { - "player_name": "Ben Zobrist", - "track": {"card_type": "util"}, - "current_tier": 2, - "current_value": 80, - "next_threshold": 120, - } - result = format_refractor_entry(util_state) - assert "util" in result, ( - f"Unknown card_type should appear verbatim as the formula label, got: {result!r}" - ) + assert "Edwin Diaz" in result + assert "45/60" in result def test_unknown_card_type_does_not_crash(self): - """ - Any unknown card_type must produce a valid two-line string without - raising an exception. - """ + """Unknown card_type produces a valid two-line string.""" state = { "player_name": "Test Player", "track": {"card_type": "dh"}, @@ -793,7 +729,10 @@ async def test_refractor_status_empty_roster(mock_bot, mock_interaction): team = {"id": 1, "sname": "Test"} with patch("cogs.refractor.get_team_by_owner", new=AsyncMock(return_value=team)): - with patch("cogs.refractor.db_get", new=AsyncMock(return_value={"cards": []})): + with patch( + "cogs.refractor.db_get", + new=AsyncMock(return_value={"items": [], "count": 0}), + ): await cog.refractor_status.callback(cog, mock_interaction) call_kwargs = mock_interaction.edit_original_response.call_args