LVPWB/test_lvpwb.py

1672 lines
86 KiB
Python

import contextlib
import io
import json
import os
import tempfile
import unittest
from datetime import datetime
from pathlib import Path
from unittest.mock import patch
import lvpwb as app
class RosterTests(unittest.TestCase):
def test_rosters_load_from_uniform_json_payloads(self):
self.assertEqual(len(app.load_roster("sd1")), 36)
self.assertEqual(len(app.load_roster("sd2")), 65)
self.assertEqual(len(app.load_roster("sd3")), 43)
for game_id, game in app.GAMES.items():
payload = json.loads((app.ROOT / game["roster"]).read_text(encoding="utf-8"))
self.assertEqual(set(payload), {"game_id", "game", "wrestlers"})
self.assertEqual(payload["game_id"], game_id)
self.assertEqual(payload["game"], game["title"])
class MarkdownTests(unittest.TestCase):
def make_card(self, path: Path) -> dict:
def profile_fields(match_type: str) -> dict:
definition = app.match_type_definition("sd2", match_type)
return {"base_rating": definition["base_rating"],
"valid_outcomes": list(definition["outcomes"])}
return {
"game_id": "sd2",
"show": 'The "Big" Show',
"date": "2026-10-25",
"path": str(path),
"matches": [
{"title": "Opener", "type": "Singles", "sides": [["The Rock"], ["Kane"]],
"winner_side": 0, "finish": "pinfall", "promo": None, "image": "",
"ranking_division": None, "title_defense": False, "rivalry_payoff": False,
"match_time_minutes": None, "near_falls": 0, "finishers": 0, "rating": None,
**profile_fields("Singles")},
{"title": "", "type": "Tag Team", "sides": [["Edge", "Christian"],
["The Hardy Boyz", "Tazz"]], "winner_side": None, "finish": "", "promo": None,
"image": "", "ranking_division": None, "title_defense": False,
"rivalry_payoff": False, "match_time_minutes": None, "near_falls": 0,
"finishers": 0, "rating": None, **profile_fields("Tag Team")},
{"title": "", "type": "Triple Threat", "sides": [], "winner_side": None,
"finish": "", "promo": None, "image": "", "ranking_division": None,
"title_defense": False, "rivalry_payoff": False, "match_time_minutes": None,
"near_falls": 0, "finishers": 0, "rating": None,
**profile_fields("Triple Threat")},
],
}
def test_markdown_round_trip_including_incomplete_match(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = self.make_card(path)
app.save_card(card)
loaded = app.load_card(path)
self.assertEqual(loaded["show"], card["show"])
self.assertEqual(loaded["game_id"], card["game_id"])
self.assertEqual(loaded["matches"], card["matches"])
def test_load_pads_short_card_to_three_matches(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "short.md"
card = self.make_card(path)
card["matches"] = card["matches"][:1]
app.save_card(card)
loaded = app.load_card(path)
self.assertEqual(len(loaded["matches"]), 3)
self.assertEqual(loaded["matches"][1]["sides"], [])
def test_render_shows_match_names_and_recorded_winner(self):
card = self.make_card(Path("card.md"))
with patch.object(app, "_render_image", return_value=([], 0)):
rendered = app.render_card(card)
flashing = app.render_card(card, flash=True)
self.assertIn("Match 1 — Opener", rendered)
self.assertIn("The Rock", rendered)
self.assertIn("WIN!", flashing)
self.assertIn("UP NEXT", rendered)
main_event_position = rendered.index("Match 3")
second_match_position = rendered.index("Match 2")
opener_position = rendered.index("Match 1 — Opener")
self.assertLess(main_event_position, second_match_position)
self.assertLess(second_match_position, opener_position)
class RatingTests(unittest.TestCase):
def test_fixed_match_type_bases(self):
self.assertEqual(app.base_match_rating("Singles"), 4.5)
self.assertEqual(app.base_match_rating("TLC"), 7.5)
self.assertEqual(app.base_match_rating("Hell in a Cell"), 9.5)
def test_time_near_falls_and_finishers_drive_match_rating(self):
match = {"type": "Singles", "match_time_minutes": 12.9,
"near_falls": 2, "finishers": 3, "title_defense": False}
self.assertEqual(app.calculate_match_rating(match), 7.6)
match["title_defense"] = True
self.assertEqual(app.calculate_match_rating(match), 11.4)
def test_combined_live_bonus_is_capped_before_title_multiplier(self):
match = {"type": "Singles", "match_time_minutes": 200,
"near_falls": 100, "finishers": 100, "title_defense": True}
self.assertEqual(app.calculate_match_rating(match), 21.8)
def test_match_duration_and_counts_validate(self):
self.assertEqual(app.parse_match_minutes("12.5"), 12.5)
self.assertEqual(app.parse_match_minutes("0"), 0)
for invalid in ("-1", "nan", "inf", "abc"):
self.assertIsNone(app.parse_match_minutes(invalid))
self.assertEqual(app.parse_action_count("3"), 3)
self.assertIsNone(app.parse_action_count("-1"))
self.assertIsNone(app.parse_action_count("2.5"))
def test_ppv_uses_weighted_three_match_score_only_when_all_are_rated(self):
card = {"show": "WWF PPV", "matches": [{"rating": value}
for value in (10.0, 20.0, 30.0)]}
self.assertEqual(app.calculate_ppv_rating(card), 23.0)
card["matches"][1]["rating"] = None
self.assertIsNone(app.calculate_ppv_rating(card))
card["show"] = "Monday Night Raw"
self.assertIsNone(app.calculate_ppv_rating(card))
def test_match_and_ppv_ratings_round_trip_in_markdown(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "ppv.md"
card = MarkdownTests().make_card(path)
card["show"] = "WWF PPV"
for match, rating in zip(card["matches"], (10.0, 20.0, 30.0)):
match.update(match_time_minutes=12.5, near_falls=2, finishers=1, rating=rating)
app.save_card(card)
loaded = app.load_card(path)
self.assertEqual(loaded["matches"][1]["match_time_minutes"], 12.5)
self.assertEqual(loaded["matches"][1]["near_falls"], 2)
self.assertEqual(loaded["matches"][1]["finishers"], 1)
self.assertEqual(loaded["matches"][1]["rating"], 20.0)
self.assertEqual(loaded["ppv_rating"], 23.0)
with patch.object(app, "_render_image", return_value=([], 0)):
rendered = app.render_card(loaded)
self.assertIn("WWF PPV Rating: 23.0%", rendered)
def test_historical_brand_names_use_thursday_and_generic_ppv(self):
self.assertEqual(app.show_name_for_brand("SmackDown"), "Thursday Night SmackDown!")
self.assertEqual(app.show_name_for_brand("PPV"), "WWF PPV")
def test_result_prompt_records_duration_near_falls_finishers_and_rating(self):
with tempfile.TemporaryDirectory() as folder:
card = MarkdownTests().make_card(Path(folder) / "card.md")
answers = ["1", "1", "1", "1", "12.5", "3", "2"]
with patch("builtins.input", side_effect=answers), \
contextlib.redirect_stdout(io.StringIO()):
app.record_result(card)
match = card["matches"][1]
self.assertEqual(match["winner_side"], 0)
self.assertEqual(match["match_time_minutes"], 12.5)
self.assertEqual(match["near_falls"], 3)
self.assertEqual(match["finishers"], 2)
self.assertEqual(match["rating"], 7.3)
class MatchLabelTests(unittest.TestCase):
def test_ranking_division_alone_is_labeled_non_title(self):
match = {"title": "", "type": "Singles", "sides": [["The Undertaker"], ["Viscera"]],
"winner_side": None, "ranking_division": "European", "title_defense": False}
rendered = "\n".join(app.match_lines(match, 1, 100))
self.assertIn("NON-TITLE MATCH", rendered)
self.assertNotIn("EUROPEAN", rendered)
def test_only_explicit_title_match_displays_championship(self):
match = {"title": "European Championship", "type": "Singles",
"sides": [["The Undertaker"], ["Viscera"]], "winner_side": None,
"ranking_division": "European", "title_defense": True}
rendered = "\n".join(app.match_lines(match, 1, 100))
self.assertIn("EUROPEAN TITLE MATCH", rendered)
self.assertNotIn("NON-TITLE MATCH", rendered)
class PromoVoiceTests(unittest.TestCase):
def test_rock_austin_and_triple_h_have_signature_voices(self):
with patch.object(app.random, "choice", side_effect=lambda options: options[0]):
rock = app.promo_line_for("The Rock", "Mankind", "openers")
austin = app.promo_line_for("Stone Cold Steve Austin", "The Rock", "challenges")
triple_h = app.promo_line_for("Triple H", "Kane", "openers")
self.assertIn("jabroni", rock.lower())
with patch.object(app.random, "choice", side_effect=lambda options: options[0]):
rock_with_opponent = app.promo_line_for("The Rock", "Jeff Hardy", "openers")
self.assertIn("jabroni Jeff Hardy—", rock_with_opponent)
self.assertIn("bottom line", austin.lower())
self.assertIn("I am the Game", triple_h)
def test_mankind_has_persecuted_little_guy_twisted_idiom_and_pain(self):
with patch.object(app.random, "choice", side_effect=lambda options: options[0]):
line = app.promo_line_for("Mick Foley", "The Rock", "challenges")
self.assertIn("little guy", line)
self.assertIn("worm gets the bird", line)
self.assertIn("bricks", line)
def test_unprofiled_wrestler_uses_generic_fallback(self):
with patch.object(app.random, "choice", side_effect=lambda options: options[0]):
line = app.promo_line_for("Debra", "The Rock", "openers")
self.assertIn(app.PROMO_OPENERS[0], line)
def test_every_profile_formats_for_each_promo_turn(self):
with patch.object(app.random, "choice", side_effect=lambda options: options[0]):
for alias in app.PROMO_PROFILE_ALIASES:
for moment in ("openers", "challenges", "retorts"):
line = app.promo_line_for(alias, "Jeff Hardy", moment)
self.assertTrue(line)
self.assertNotIn(":", line, f"Unexpected speaker-like colon for {alias}: {line}")
for profile_key, profile in app.PROMO_PROFILES.items():
for moment in ("openers", "challenges", "retorts"):
for template in profile[moment]:
line = template.format(opponent="Jeff Hardy", wrestler="Test Wrestler")
self.assertNotIn(":", line, f"Unexpected colon in {profile_key}: {line}")
for bank in (app.PROMO_OPENERS, app.PROMO_CHALLENGES, app.PROMO_RETORTS):
for line in bank:
self.assertNotIn(":", line)
class MatchOutcomeTests(unittest.TestCase):
def test_match_types_only_offer_legal_win_conditions(self):
self.assertEqual(app.allowed_match_outcomes("Ladder"), ("Item retrieval",))
self.assertEqual(app.allowed_match_outcomes("TLC"), ("Item retrieval",))
self.assertEqual(app.allowed_match_outcomes("Table"), ("Table break",))
self.assertEqual(app.allowed_match_outcomes("Battle Royal"),
("Last wrestler standing",))
self.assertEqual(app.allowed_match_outcomes("Cage"), ("Escape the cage",))
self.assertEqual(app.allowed_match_outcomes("Singles"), ("Pinfall", "Submission"))
def test_auto_sim_uses_only_valid_finish_and_randomizes_match_stats(self):
card = MarkdownTests().make_card(Path("card.md"))
card["matches"][1].update(type="Ladder", sides=[["Edge"], ["Kane"]])
with patch.object(app.random, "choices", return_value=[0]), \
patch.object(app.random, "choice", side_effect=lambda options: options[0]), \
patch.object(app.random, "randint", side_effect=lambda low, high: low):
result = app.simulate_match_result(card, 1)
self.assertEqual(result["finish"], "Item retrieval")
self.assertIn(result["winner_side"], (0, 1))
self.assertIsNotNone(result["match_time_minutes"])
self.assertIsNotNone(result["rating"])
def test_auto_simulate_all_completes_and_persists_pending_matches(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = MarkdownTests().make_card(path)
card["matches"][1]["type"] = "Ladder"
with patch.object(app.random, "choices", return_value=[1]), \
patch.object(app.random, "choice", side_effect=lambda options: options[0]), \
patch.object(app.random, "randint", side_effect=lambda low, _high: low), \
contextlib.redirect_stdout(io.StringIO()):
app.simulate_pending_matches(card)
match = app.load_card(path)["matches"][1]
self.assertEqual(match["winner_side"], 1)
self.assertEqual(match["finish"], "Item retrieval")
self.assertIsNotNone(match["rating"])
def test_season_simulation_weights_higher_ranked_sides_but_one_off_is_even(self):
division = "European"
season = app.new_season_state("Sim Test", "sd1", season_id="sim-test")
for wrestler, wins, losses in (("Kane", 5, 0), ("Mankind", 0, 5)):
season["rankings"][division][app.side_identity([wrestler])] = {
"members": [wrestler], "wins": wins, "losses": losses,
"recent": ["W"] * wins + ["L"] * losses,
}
match = {"ranking_division": division, "title_defense": False,
"sides": [["Kane"], ["Mankind"]]}
weights = app.season_simulation_weights(season, match)
self.assertGreater(weights[0], weights[1])
self.assertEqual(app.season_simulation_weights(None, match), [1.0, 1.0])
def test_ranked_non_title_bouts_are_not_labeled_contender_matches(self):
season = app.new_season_state("Test Season", "sd1", season_id="rank-label-test")
candidates = app.season_booking_candidates(season, app.load_roster("sd1"), "Raw", set())
ranked = [candidate["match"] for candidate in candidates if candidate["kind"] == "ranked"]
self.assertTrue(ranked)
self.assertTrue(all(match["title"] == "" for match in ranked))
self.assertTrue(all(not match["title_defense"] for match in ranked))
def test_season_card_exit_label_changes_after_all_matches_complete(self):
card = MarkdownTests().make_card(Path("season.md"))
card["season_id"] = "season-test"
self.assertEqual(app.season_card_exit_label(card), "Return to Season Menu")
for match in card["matches"]:
match["winner_side"] = 0
self.assertEqual(app.season_card_exit_label(card), "Advance to Next Show")
card.pop("season_id")
self.assertEqual(app.season_card_exit_label(card), "Back to main menu")
class YesNoPromptTests(unittest.TestCase):
def test_yes_no_prompt_accepts_short_and_long_answers_case_insensitively(self):
with patch("builtins.input", side_effect=["YES", "n"]):
self.assertTrue(app.ask_yes_no("Title defense?"))
self.assertFalse(app.ask_yes_no("Title defense?"))
def test_yes_no_prompt_reprompts_invalid_input_and_keeps_default_on_blank(self):
with contextlib.redirect_stdout(io.StringIO()), \
patch("builtins.input", side_effect=["maybe", "y", ""]):
self.assertTrue(app.ask_yes_no("Title defense?", default=False))
self.assertTrue(app.ask_yes_no("Title defense?", default=True))
class LocationTests(unittest.TestCase):
def ask_location(self, answers: list[str]) -> tuple[str, int]:
with contextlib.redirect_stdout(io.StringIO()), \
patch("builtins.input", side_effect=answers):
return app.ask_location()
def test_random_location_uses_known_city_range(self):
city, attendance = app.random_location()
self.assertIn(city, app.CITY_ATTENDANCE_RANGES)
low, high = app.CITY_ATTENDANCE_RANGES[city]
self.assertGreaterEqual(attendance, low)
self.assertLessEqual(attendance, high)
def test_manual_city_and_attendance_are_used_as_entered(self):
self.assertEqual(self.ask_location(["Boston, MA", "15,450"]), ("Boston, MA", 15450))
def test_random_city_prompt_skips_attendance_question(self):
with contextlib.redirect_stdout(io.StringIO()), \
patch("builtins.input", side_effect=["r"]):
city, attendance = app.ask_location()
self.assertIn(city, app.CITY_ATTENDANCE_RANGES)
low, high = app.CITY_ATTENDANCE_RANGES[city]
self.assertTrue(low <= attendance <= high)
def test_random_attendance_follows_entered_city_range(self):
city, attendance = self.ask_location(["Osaka, Japan", "r"])
self.assertEqual(city, "Osaka, Japan")
low, high = app.CITY_ATTENDANCE_RANGES["Osaka, Japan"]
self.assertTrue(low <= attendance <= high)
def test_invalid_attendance_reprompts(self):
self.assertEqual(self.ask_location(["Tokyo, Japan", "abc", "0", "18000"]),
("Tokyo, Japan", 18000))
def test_unknown_city_random_attendance_uses_default_range(self):
low, high = app.DEFAULT_ATTENDANCE_RANGE
for _ in range(30):
self.assertTrue(low <= app.random_attendance("Springfield, OR") <= high)
def test_markdown_preserves_city_and_attendance(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = MarkdownTests().make_card(path)
card["city"] = "Toronto, ON"
card["attendance"] = 17500
app.save_card(card)
loaded = app.load_card(path)
self.assertEqual(loaded["city"], "Toronto, ON")
self.assertEqual(loaded["attendance"], 17500)
def test_legacy_card_without_location_still_loads(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "legacy.md"
card = MarkdownTests().make_card(path)
app.save_card(card)
text = path.read_text(encoding="utf-8")
path.write_text("\n".join(
line for line in text.splitlines()
if not line.startswith(("- City:", "- Attendance:"))
) + "\n", encoding="utf-8")
loaded = app.load_card(path)
self.assertEqual(loaded["city"], "")
self.assertIsNone(loaded["attendance"])
def test_header_and_listing_show_location_next_to_show_name(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = MarkdownTests().make_card(path)
card["city"] = "Osaka, Japan"
card["attendance"] = 12000
app.save_card(card)
with patch.object(app, "_render_image", return_value=([], 0)):
header = "\n".join(app.header_lines(card, 100))
self.assertIn('The "Big" Show', header)
self.assertIn("Osaka, Japan", header)
self.assertIn("12,000 fans", header)
summary = app.card_summary(path)
self.assertIn('The "Big" Show', summary)
self.assertIn("Osaka, Japan", summary)
self.assertIn("12,000 fans", summary)
class ScreenshotTests(unittest.TestCase):
def make_screenshots(self, folder: Path) -> dict[str, Path]:
names = {
"old": "WWF SmackDown 2026-01-01-10-00-00.png",
"newest": "WWF SmackDown 2026-10-26-21-30-00.png",
"newer": "WWF SmackDown 2026-10-26-09-15-00.png",
"dated": "No stamp in this name.png",
}
paths = {key: folder / value for key, value in names.items()}
for path in paths.values():
path.write_bytes(b"png")
return paths
def test_parse_screenshot_stamp_reads_filename(self):
stamp = app.parse_screenshot_stamp("WWF SmackDown 2026-10-26-21-30-00.png")
self.assertEqual(stamp, datetime(2026, 10, 26, 21, 30, 0).astimezone())
self.assertIsNone(app.parse_screenshot_stamp("No stamp in this name.png"))
def test_session_filter_keeps_only_shots_after_since_with_mtime_fallback(self):
with tempfile.TemporaryDirectory() as folder:
directory = Path(folder)
paths = self.make_screenshots(directory)
fall_back = datetime(2026, 10, 26, 8, 0, 0).astimezone()
os.utime(paths["dated"], (fall_back.timestamp(), fall_back.timestamp()))
since = datetime(2026, 10, 26, 7, 0, 0).astimezone()
found = app.session_screenshots(since=since, directory=directory)
self.assertEqual(found, [paths["newest"], paths["newer"], paths["dated"]])
self.assertNotIn(paths["old"], app.session_screenshots(
since=datetime(2026, 10, 26, 9, 0, 0).astimezone(), directory=directory))
def test_missing_screenshot_directory_lists_nothing(self):
self.assertEqual(app.session_screenshots(directory=Path("/nonexistent-dir")), [])
def test_builtin_games_use_their_emulator_screenshot_directories(self):
self.assertEqual(app.screenshot_directory_for_game("sd1"),
app.DUCKSTATION_SCREENSHOT_DIR)
self.assertEqual(app.screenshot_directory_for_game("sd2"),
app.DUCKSTATION_SCREENSHOT_DIR)
self.assertEqual(app.screenshot_directory_for_game("sd3"),
app.PCSX2_SCREENSHOT_DIR)
self.assertTrue(app.game_has_screenshots("sd3"))
def test_attach_uses_the_games_screenshot_directory(self):
with tempfile.TemporaryDirectory() as folder:
card = MarkdownTests().make_card(Path(folder) / "card.md")
card["game_id"] = "sd3"
with patch.object(app, "choose_match_index", return_value=0), \
patch.object(app, "choose_screenshot", return_value=Path(folder) / "shot.png") as choose, \
contextlib.redirect_stdout(io.StringIO()):
app.attach_match_image(card)
choose.assert_called_once_with(app.PCSX2_SCREENSHOT_DIR)
self.assertEqual(card["matches"][0]["image"], str(Path(folder) / "shot.png"))
def test_attach_flow_prompts_yes_and_stores_chosen_image(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = MarkdownTests().make_card(path)
chosen = Path(folder) / "WWF SmackDown 2026-10-26-21-30-00.png"
chosen.write_bytes(b"png")
with contextlib.redirect_stdout(io.StringIO()), \
patch("builtins.input", side_effect=["2", "1"]), \
patch.object(app, "session_screenshots", return_value=[chosen]), \
patch.object(app, "DUCKSTATION_SCREENSHOT_DIR", Path(folder)):
app.attach_match_image(card)
self.assertEqual(card["matches"][1]["image"], str(chosen))
self.assertIn(f'- Image: "{chosen}"', path.read_text(encoding="utf-8"))
self.assertEqual(app.load_card(path)["matches"][1]["image"], str(chosen))
def test_attach_declines_when_game_has_no_screenshot_directory(self):
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = MarkdownTests().make_card(path)
card["game_id"] = "unknown-other-game"
before = json.dumps(card["matches"])
with contextlib.redirect_stdout(io.StringIO()), \
patch("builtins.input", side_effect=RuntimeError("unexpected prompt")):
app.attach_match_image(card)
self.assertEqual(json.dumps(card["matches"]), before)
class RandomShowTests(unittest.TestCase):
def test_randomized_card_uses_selected_games_roster_without_repeats(self):
with tempfile.TemporaryDirectory() as folder:
with patch.object(app, "CARD_DIR", Path(folder)), \
patch.object(app, "choose_game", return_value="sd2"), \
patch.object(app.random, "seed", 42), \
contextlib.redirect_stdout(io.StringIO()):
card = app.create_random_card()
roster = set(app.load_roster("sd2"))
self.assertEqual(len(card["matches"]), 3)
self.assertIn(card["show"], {"Monday Night Raw", "Thursday Night SmackDown!",
"WWF PPV"})
for match in card["matches"]:
wrestlers = [name for side in match["sides"] for name in side]
self.assertTrue(set(wrestlers) <= roster)
self.assertEqual(len(wrestlers), len(set(wrestlers)))
self.assertIsNone(match["winner_side"])
class SelectionPromptTests(unittest.TestCase):
def test_numeric_choice_shows_exact_labels_and_repeats_them_on_invalid_input(self):
with patch("builtins.input", side_effect=["3", "1"]), \
contextlib.redirect_stdout(io.StringIO()) as output:
selected = app.ask_choice("Winning method", ["Pinfall", "Submission"])
self.assertEqual(selected, 0)
self.assertEqual(output.getvalue().count("1. Pinfall"), 2)
self.assertIn("2. Submission", output.getvalue())
self.assertIn("Enter a number from 1 to 2.", output.getvalue())
def test_manual_wrestler_selection_is_alphabetical(self):
roster = app.load_roster("sd2")
ordered = sorted(roster, key=str.casefold)
with patch("builtins.input", side_effect=["1", "2"]), \
contextlib.redirect_stdout(io.StringIO()) as output:
sides = app.choose_sides((1, 1), roster)
self.assertEqual(sides, [[ordered[0]], [ordered[1]]])
listing = output.getvalue()
self.assertLess(listing.index(ordered[0]), listing.index(ordered[1]))
def test_result_mode_displays_manual_and_auto_sim_options(self):
with tempfile.TemporaryDirectory() as folder:
card = MarkdownTests().make_card(Path(folder) / "card.md")
with patch("builtins.input", side_effect=["1", "2"]), \
patch.object(app.random, "choices", return_value=[0]), \
patch.object(app.random, "choice", side_effect=lambda options: options[0]), \
patch.object(app.random, "randint", side_effect=lambda low, _high: low), \
contextlib.redirect_stdout(io.StringIO()) as output:
app.record_result(card)
self.assertIn("1. Enter result manually", output.getvalue())
self.assertIn("2. Auto-simulate result", output.getvalue())
self.assertIsNotNone(card["matches"][1]["rating"])
class SeasonModeTests(unittest.TestCase):
def season(self, **kwargs) -> dict:
return app.new_season_state(
kwargs.get("name", "Test Season"), "sd1",
kwargs.get("champions", {}), kwargs.get("season_id", "test-season"),
)
def test_initial_single_title_champion_options_match_displayed_indices(self):
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=0), \
patch.object(app, "choose_roster_member", return_value="Al Snow") as choose:
self.assertEqual(app.choose_initial_champion("European", "sd1", set()), ["Al Snow"])
choose.assert_called_once()
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=1), \
patch.object(app.random, "choice", return_value="The Rock"), \
patch.object(app, "choose_roster_member") as choose:
self.assertEqual(app.choose_initial_champion("European", "sd1", set()), ["The Rock"])
choose.assert_not_called()
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=2), \
patch.object(app, "choose_roster_member") as choose:
self.assertIsNone(app.choose_initial_champion("European", "sd1", set()))
choose.assert_not_called()
def test_initial_tag_team_champion_options_match_displayed_indices(self):
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=0), \
patch.object(app, "choose_roster_member", side_effect=["Al Snow", "The Rock"]):
self.assertEqual(app.choose_initial_champion("Tag Team", "sd1", set()),
["Al Snow", "The Rock"])
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=1), \
patch.object(app.random, "sample", return_value=["Al Snow", "The Rock"]), \
patch.object(app, "choose_roster_member") as choose:
self.assertEqual(app.choose_initial_champion("Tag Team", "sd1", set()),
["Al Snow", "The Rock"])
choose.assert_not_called()
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=2), \
patch.object(app, "choose_roster_member") as choose:
self.assertIsNone(app.choose_initial_champion("Tag Team", "sd1", set()))
choose.assert_not_called()
def test_raw_smackdown_ppv_cycle_repeats(self):
self.assertEqual([app.season_show_brand(n) for n in range(1, 7)],
["Raw", "SmackDown", "PPV", "Raw", "SmackDown", "PPV"])
def test_wins_and_losses_update_division_rankings(self):
season = self.season()
match = {"ranking_division": "Intercontinental", "winner_side": 0,
"sides": [["The Rock"], ["Kane"]], "title_defense": False,
"rivalry_payoff": False}
app.record_season_match(season, {"season_show": "Raw"}, match, 0)
entries = app.season_ranking_entries(season, "Intercontinental")
self.assertEqual(entries[0]["members"], ["The Rock"])
self.assertEqual(entries[0]["wins"], 1)
self.assertEqual(entries[1]["losses"], 1)
def test_title_defenses_increment_and_title_changes_reset_current_reign(self):
division = "World Wrestling Federation"
season = self.season(champions={division: ["The Rock"]})
successful = {"ranking_division": division, "title_defense": True,
"rivalry_payoff": False, "sides": [["The Rock"], ["Kane"]],
"winner_side": 0}
app.record_season_match(season, {"season_show": "Raw"}, successful, 0)
belt = season["champions"][division]
self.assertEqual(belt["successful_defenses"], 1)
self.assertEqual(belt["defense_history"][app.side_identity(["The Rock"])]
["successful_defenses"], 1)
new_champion = dict(successful, winner_side=1)
app.record_season_match(season, {"season_show": "PPV"}, new_champion, 1)
self.assertEqual(belt["holder"], ["Kane"])
self.assertEqual(belt["successful_defenses"], 0)
self.assertEqual(belt["defense_history"][app.side_identity(["The Rock"])]
["successful_defenses"], 1)
def test_tag_team_champion_is_ranked_and_defenses_count_for_team(self):
division = "Tag Team"
team = ["Edge", "Christian"]
season = self.season(champions={division: team})
match = {"ranking_division": division, "title_defense": True,
"rivalry_payoff": False, "sides": [team, ["The Hardy Boyz", "Tazz"]],
"winner_side": 0}
app.record_season_match(season, {"season_show": "SmackDown"}, match, 0)
self.assertEqual(season["champions"][division]["successful_defenses"], 1)
self.assertEqual(app.season_ranking_entries(season, division)[0]["members"], team)
def test_others_pool_is_unranked_and_excludes_title_division_participants(self):
season = self.season(champions={"European": ["Kane"]})
season["rankings"]["European"][app.side_identity(["The Rock"])] = {
"members": ["The Rock"], "wins": 1, "losses": 0, "recent": ["W"],
}
pool = app.season_other_pool(season)
self.assertNotIn("Kane", pool)
self.assertNotIn("The Rock", pool)
self.assertIn("Val Venis", pool)
self.assertNotIn("Others", app.CHAMPIONSHIPS)
def test_ppv_modestly_boosts_title_and_rivalry_booking(self):
self.assertGreater(app.title_defense_weight("title", "PPV"),
app.title_defense_weight("title", "Raw"))
self.assertGreater(app.title_defense_weight("rivalry", "PPV"),
app.title_defense_weight("rivalry", "SmackDown"))
self.assertEqual(app.title_defense_weight("pool", "PPV"),
app.title_defense_weight("pool", "Raw"))
def test_title_feud_uses_highest_ranked_eligible_challenger(self):
division = "World Wrestling Federation"
season = self.season(champions={division: ["The Rock"]})
table = season["rankings"][division]
table[app.side_identity(["Kane"])] = {
"members": ["Kane"], "wins": 5, "losses": 1, "recent": ["W", "W"],
}
table[app.side_identity(["Mankind"])] = {
"members": ["Mankind"], "wins": 2, "losses": 2, "recent": ["L", "W"],
}
candidates = app.season_booking_candidates(season, app.load_roster("sd1"), "Raw", set())
title_match = next(candidate["match"] for candidate in candidates
if candidate["kind"] == "title"
and candidate["match"]["ranking_division"] == division)
self.assertEqual(title_match["sides"], [["The Rock"], ["Kane"]])
self.assertTrue(title_match["title_defense"])
def test_vacant_championship_uses_top_two_ranked_contenders(self):
division = "European"
season = self.season()
table = season["rankings"][division]
for wrestler, wins in (("Kane", 5), ("Mankind", 3), ("The Rock", 1)):
table[app.side_identity([wrestler])] = {
"members": [wrestler], "wins": wins, "losses": 0, "recent": ["W"],
}
candidates = app.season_booking_candidates(season, app.load_roster("sd1"), "Raw", set())
title = next(item["match"] for item in candidates
if item["kind"] == "title" and item["match"]["ranking_division"] == division)
self.assertEqual(title["sides"], [["Kane"], ["Mankind"]])
self.assertTrue(title["title"].startswith("Vacant "))
self.assertTrue(title["title_defense"])
def test_ppv_rivalry_match_is_marked_as_storyline_payoff(self):
season = self.season()
sides = [["The Rock"], ["Stone Cold Steve Austin"]]
rivalry_id = app.season_rivalry_id(sides)
season["rivalries"][rivalry_id] = {
"sides": sides, "meetings": 3, "winner_counts": {}, "resolved": False,
"ranking_division": "World Wrestling Federation",
}
candidates = app.season_booking_candidates(season, app.load_roster("sd1"), "PPV", set())
feud = next(candidate["match"] for candidate in candidates
if candidate["kind"] == "rivalry")
self.assertTrue(feud["rivalry_payoff"])
self.assertEqual(feud["title"], "Rivalry Finale")
def test_ppv_title_match_can_resolve_an_existing_title_feud(self):
division = "World Wrestling Federation"
season = self.season(champions={division: ["The Rock"]})
challenger = ["Kane"]
season["rankings"][division][app.side_identity(challenger)] = {
"members": challenger, "wins": 3, "losses": 1, "recent": ["W", "W"],
}
sides = [["The Rock"], challenger]
season["rivalries"][app.season_rivalry_id(sides)] = {
"sides": sides, "meetings": 1, "winner_counts": {}, "resolved": False,
"ranking_division": division,
}
candidates = app.season_booking_candidates(season, app.load_roster("sd1"), "PPV", set())
title = next(item["match"] for item in candidates if item["kind"] == "title")
self.assertTrue(title["title_defense"])
self.assertTrue(title["rivalry_payoff"])
self.assertIn("Feud Finale", title["title"])
def test_booking_caps_title_defenses_at_one_weekly_and_two_on_ppv(self):
champions = {
"Tag Team": ["Road Dogg", "Billy Gunn"],
"World Wrestling Federation": ["The Rock"],
"Intercontinental": ["The Undertaker"],
"European": ["Kane"],
"Hardcore": ["Mankind"],
"Light-Heavyweight": ["X-Pac"],
"Women's": ["Chyna"],
}
season = self.season(champions=champions)
for division, holder in champions.items():
if division == "Tag Team":
challenger = ["Edge", "Christian"]
elif division == "Women's":
challenger = ["Tori"]
else:
challenger = ["Val Venis"]
season["rankings"][division][app.side_identity(challenger)] = {
"members": challenger, "wins": 3, "losses": 1, "recent": ["W", "W"],
}
with patch.object(app.random, "seed", 6):
raw = app.build_season_matches(season, "Raw")
ppv = app.build_season_matches(season, "PPV")
self.assertLessEqual(sum(bool(match["title_defense"]) for match in raw), 1)
self.assertLessEqual(sum(bool(match["title_defense"]) for match in ppv), 2)
def test_booking_a_show_persists_current_card_and_cycle_brand(self):
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
season = self.season(season_id="booking-test")
with patch.object(app, "SEASON_DIR", root / "seasons"), \
patch.object(app, "CARD_DIR", root / "cards"), \
contextlib.redirect_stdout(io.StringIO()):
card = app.book_next_season_show(season)
saved = app.load_season(season["season_id"])
loaded_card = app.load_card(Path(card["path"]))
self.assertEqual(card["season_show"], "Raw")
self.assertEqual(len(card["matches"]), 3)
self.assertEqual(saved["current_card"], card["path"])
self.assertEqual(loaded_card["season_show_number"], 1)
def test_season_save_round_trip_and_season_linked_card_metadata(self):
with tempfile.TemporaryDirectory() as folder:
season_dir, card_dir = Path(folder) / "seasons", Path(folder) / "cards"
season = self.season(champions={"European": ["Kane"]})
card = MarkdownTests().make_card(card_dir / "Raw.md")
card.update(season_id=season["season_id"], season_show="Raw", season_show_number=1)
with patch.object(app, "SEASON_DIR", season_dir), patch.object(app, "CARD_DIR", card_dir):
app.save_season(season)
app.save_card(card)
loaded_season = app.load_season(season["season_id"])
loaded_card = app.load_card(Path(card["path"]))
self.assertEqual(loaded_season["champions"]["European"]["holder"], ["Kane"])
self.assertEqual(loaded_card["season_id"], season["season_id"])
self.assertEqual(loaded_card["season_show"], "Raw")
self.assertEqual(loaded_card["season_show_number"], 1)
def test_syncing_completed_season_card_updates_results_only_once(self):
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
season = self.season(season_id="sync-test")
card = MarkdownTests().make_card(root / "Raw.md")
card.update(season_id=season["season_id"], season_show="Raw",
season_show_number=1)
for match in card["matches"]:
match["winner_side"] = 0
match["ranking_division"] = "European"
season["current_card"] = str(root / "Raw.md")
with patch.object(app, "SEASON_DIR", root / "seasons"):
app.save_season(season)
app.sync_season_card_results(card)
after_first = app.load_season(season["season_id"])
app.sync_season_card_results(card)
after_second = app.load_season(season["season_id"])
self.assertEqual(after_first["next_show_number"], 2)
self.assertEqual(after_first["current_card"], "")
self.assertEqual(len(after_first["processed_results"]), 3)
self.assertEqual(after_second["rankings"], after_first["rankings"])
self.assertEqual(len(after_second["processed_results"]), 3)
class SeasonControlTests(unittest.TestCase):
def season(self, season_id="control-test"):
return app.new_season_state("Control Test", "sd2", season_id=season_id)
def test_new_and_legacy_seasons_default_to_booker_control(self):
season = self.season()
self.assertEqual(season["control_mode"], "booker")
self.assertIsNone(season["player_wrestler"])
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder)):
season.pop("control_mode")
season.pop("player_wrestler")
app.save_season(season)
loaded = app.load_season(season["season_id"])
self.assertEqual(loaded["control_mode"], "booker")
self.assertIsNone(loaded["player_wrestler"])
def test_startup_choice_and_toggle_persist_wrestler_then_booker(self):
season = self.season()
expected_wrestler = sorted(app.load_roster("sd2"), key=str.casefold)[0]
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder)), \
patch.object(app, "ask_choice", side_effect=[1, 0]):
app.choose_season_control_mode(season)
self.assertEqual(season["control_mode"], "wrestler")
self.assertEqual(season["player_wrestler"], expected_wrestler)
self.assertEqual(app.load_season(season["season_id"])["player_wrestler"],
expected_wrestler)
app.toggle_season_control_mode(season)
self.assertEqual(app.load_season(season["season_id"])["control_mode"], "booker")
with patch.object(app, "ask_choice", return_value=0):
app.toggle_season_control_mode(season)
saved = app.load_season(season["season_id"])
self.assertEqual(saved["control_mode"], "wrestler")
self.assertEqual(saved["player_wrestler"], expected_wrestler)
def test_season_menu_displays_current_control_mode_and_toggles_back(self):
season = self.season()
season["control_mode"] = "wrestler"
season["player_wrestler"] = "The Rock"
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder)), \
patch.object(app, "ask_choice", side_effect=[2, 3]) as choose, \
contextlib.redirect_stdout(io.StringIO()):
app.save_season(season)
app.run_season(season)
saved = app.load_season(season["season_id"])
self.assertIn("Toggle Control Mode — Current: Wrestler (The Rock)",
choose.call_args_list[0].kwargs["labels"])
self.assertEqual(season["control_mode"], "booker")
self.assertEqual(saved["control_mode"], "booker")
def test_wrestler_control_simulates_non_player_matches_and_preserves_player_match(self):
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder) / "seasons"):
season = self.season("wrestler-control-results")
season["control_mode"] = "wrestler"
season["player_wrestler"] = "The Rock"
card = MarkdownTests().make_card(Path(folder) / "card.md")
card["season_id"] = season["season_id"]
card["season_show_number"] = 1
card["title_definitions"] = season["title_definitions"]
card["matches"][0].update({
"sides": [["The Rock"], ["Kane"]], "winner_side": None,
})
card["matches"][1].update({
"sides": [["Edge"], ["Christian"]], "winner_side": None,
})
card["matches"][2].update({
"sides": [["Tazz"], ["Undertaker"]], "winner_side": None,
})
season["current_card"] = card["path"]
app.save_season(season)
app.save_card(card)
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(app.record_match_result(card, 1, auto_simulate=False))
app.simulate_non_player_matches(card, season)
self.assertIsNone(card["matches"][0]["winner_side"])
self.assertIsNotNone(card["matches"][1]["winner_side"])
self.assertIsNotNone(card["matches"][2]["winner_side"])
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(app.record_match_result(card, 0, auto_simulate=True))
captured = {}
def exit_card(_prompt, choices, labels=None):
captured["choices"] = choices
captured["labels"] = labels
return 4
with patch.object(app, "ask_choice", side_effect=exit_card), \
contextlib.redirect_stdout(io.StringIO()):
self.assertTrue(app.resume_card(card))
self.assertNotIn("edit", captured["choices"])
self.assertNotIn("simulate all", captured["choices"])
self.assertIn("Record your match result", captured["labels"])
with patch.object(app, "ask_choice", return_value=0), \
patch.object(app, "choose_match_outcome", return_value="Pinfall"), \
patch.object(app, "prompt_match_minutes", return_value=10), \
patch.object(app, "prompt_action_count", return_value=1), \
contextlib.redirect_stdout(io.StringIO()):
app.record_result(card, match_index=0)
self.assertEqual(card["matches"][0]["winner_side"], 0)
def test_absent_wrestler_causes_all_pending_matches_to_be_simulated(self):
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder) / "seasons"):
season = self.season("wrestler-absent")
season["control_mode"] = "wrestler"
season["player_wrestler"] = "The Rock"
card = MarkdownTests().make_card(Path(folder) / "card.md")
card["season_id"] = season["season_id"]
card["season_show_number"] = 1
card["title_definitions"] = season["title_definitions"]
for match, sides in zip(card["matches"], [
[["Kane"], ["Undertaker"]],
[["Edge"], ["Christian"]],
[["Tazz"], ["Big Show"]],
]):
match.update({"sides": sides, "winner_side": None})
season["current_card"] = card["path"]
app.save_season(season)
app.save_card(card)
with contextlib.redirect_stdout(io.StringIO()):
app.simulate_non_player_matches(card, season)
self.assertTrue(all(match["winner_side"] is not None for match in card["matches"]))
updated = app.load_season(season["season_id"])
self.assertEqual(updated["current_card"], "")
self.assertEqual(updated["next_show_number"], 2)
class LightHeavyweightTests(unittest.TestCase):
def test_builtin_season_selects_and_snapshots_eligible_wrestlers(self):
def select_eligible(profile):
profile["light_heavyweight_roster"] = ["X-Pac", "Jeff Hardy"]
def initial_champion(division, _game_id, _used, _profile):
return ["X-Pac"] if division == "Light-Heavyweight" else None
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder)), \
patch.object(app, "choose_game", return_value="sd1"), \
patch.object(app, "ask", return_value="Lightweight Season"), \
patch.object(app, "edit_light_heavyweight_roster", side_effect=select_eligible), \
patch.object(app, "choose_initial_champion", side_effect=initial_champion), \
contextlib.redirect_stdout(io.StringIO()):
season = app.create_season()
loaded = app.load_season(season["season_id"])
self.assertEqual(season["game_config"]["light_heavyweight_roster"],
["X-Pac", "Jeff Hardy"])
self.assertEqual(
app.eligible_roster_for_division(
"sd1", "Light-Heavyweight", profile=loaded["game_config"]
),
["X-Pac", "Jeff Hardy"],
)
self.assertEqual(loaded["champions"]["Light-Heavyweight"]["holder"], ["X-Pac"])
defense = {
"ranking_division": "Light-Heavyweight", "title_defense": True,
"sides": [["X-Pac"], ["Jeff Hardy"]], "winner_side": 1,
}
self.assertTrue(app.validate_title_defense(loaded, defense))
app.record_season_match(loaded, {"season_show": "Raw"}, defense, 0)
self.assertEqual(loaded["champions"]["Light-Heavyweight"]["holder"], ["Jeff Hardy"])
def test_initial_champion_random_choice_uses_only_eligible_wrestlers(self):
profile = app.game_profile("sd1")
profile["light_heavyweight_roster"] = ["X-Pac"]
choices_seen = []
def first_choice(options):
choices_seen.append(list(options))
return options[0]
with patch.object(app, "ask_choice", return_value=1), \
patch.object(app.random, "choice", side_effect=first_choice), \
contextlib.redirect_stdout(io.StringIO()):
champion = app.choose_initial_champion(
"Light-Heavyweight", "sd1", set(), profile
)
self.assertEqual(champion, ["X-Pac"])
self.assertEqual(choices_seen, [["X-Pac"]])
def test_ineligible_wrestlers_cannot_record_or_sync_division_results(self):
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder)):
season = app.new_season_state(
"Lightweight Season", "sd1",
champions={"Light-Heavyweight": ["X-Pac"]},
season_id="lightweight-results",
)
season["game_config"]["light_heavyweight_roster"] = ["X-Pac"]
card_path = Path(folder) / "card.md"
season["current_card"] = str(card_path)
app.save_season(season)
match = {
"type": "Singles", "sides": [["X-Pac"], ["The Rock"]],
"winner_side": 1, "ranking_division": "Light-Heavyweight",
"title_defense": True,
}
card = {
"season_id": season["season_id"], "season_show_number": 1,
"season_show": "Raw", "path": str(card_path),
"matches": [match],
}
with contextlib.redirect_stdout(io.StringIO()):
self.assertFalse(app.validate_title_defense(season, match))
self.assertFalse(app.record_match_result(card, 0, auto_simulate=True))
app.sync_season_card_results(card)
updated = app.load_season(season["season_id"])
self.assertEqual(updated["champions"]["Light-Heavyweight"]["holder"], ["X-Pac"])
self.assertEqual(updated["rankings"]["Light-Heavyweight"], {})
self.assertEqual(updated["processed_results"], [])
def test_legacy_season_without_eligibility_snapshot_remains_unrestricted(self):
season = app.new_season_state("Legacy Season", "sd1", season_id="legacy-lightweight")
season["game_config"].pop("light_heavyweight_roster", None)
self.assertEqual(
app.eligible_roster_for_division(
"sd1", "Light-Heavyweight", profile=season["game_config"]
),
season["game_config"]["roster"],
)
def test_legacy_builtin_light_heavyweight_kind_migrates_without_restricting_roster(self):
season = app.new_season_state(
"Legacy Lightweight", "sd1", season_id="legacy-lightweight-kind"
)
for definitions in (season["title_definitions"],
season["game_config"]["championships"]):
next(item for item in definitions if item["name"] == "Light-Heavyweight")["kind"] = "singles"
season["game_config"].pop("light_heavyweight_roster", None)
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder)):
app.save_season(season)
loaded = app.load_season(season["season_id"])
self.assertEqual(
app.season_championship_kind(loaded, "Light-Heavyweight"),
"light_heavyweight",
)
self.assertEqual(
app.eligible_roster_for_division(
"sd1", "Light-Heavyweight", profile=loaded["game_config"]
),
loaded["game_config"]["roster"],
)
class BYOGTests(unittest.TestCase):
def setUp(self):
self._configs = app.CUSTOM_GAME_CONFIGS
self._games = dict(app.GAMES)
self._match_types = dict(app.MATCH_TYPES)
# Point the BYOG directory at a session-local temp dir so custom
# registrations survive refreshes and never touch the real edit/ folder.
self.game_dir = tempfile.TemporaryDirectory()
self.addCleanup(self.game_dir.cleanup)
patcher = patch.object(app, "CUSTOM_GAME_DIR", Path(self.game_dir.name) / "games")
patcher.start()
self.addCleanup(patcher.stop)
def tearDown(self):
app.CUSTOM_GAME_CONFIGS = self._configs
app.GAMES.clear()
app.GAMES.update(self._games)
app.MATCH_TYPES.clear()
app.MATCH_TYPES.update(self._match_types)
def config(self, **overrides) -> dict:
data = {
"game_id": "custom-fantasy-fed-rumble-royale",
"game_title": "Rumble Royale",
"federation_name": "Fantasy Fed",
"platform": "ps1",
"roster": ["Ava Crusher", "Ben Brawler", "Cara Storm", "Dash Vance"],
"match_types": [
{"name": "Scaffold", "sides": [1, 1], "base_rating": 8.5},
{"name": "Tag Tornado", "sides": [2, 2],
"outcomes": ["pinfall", "submission"]},
],
"championships": [
{"name": "Rumble Belt", "kind": "singles"},
{"name": "Fantasy Tag", "kind": "tag"},
{"name": "Fantasy Women", "kind": "women"},
],
"women_roster": ["Cara Storm"],
"light_heavyweight_roster": [],
}
data.update(overrides)
return data
def register(self, data: dict) -> dict:
saved = app.save_custom_game(data, app.CUSTOM_GAME_DIR)
app.refresh_custom_games()
return saved
def filler_match(self, match_type: str, base_rating: float | None = None) -> dict:
sized = {}
for item in app.match_type_definitions(self.config()["game_id"]):
if item["name"] == match_type:
sized = item
break
return {"title": "", "type": match_type, "sides": [], "winner_side": None,
"finish": "", "promo": None, "image": "", "ranking_division": None,
"title_defense": False, "rivalry_payoff": False,
"match_time_minutes": None, "near_falls": 0, "finishers": 0,
"rating": None,
"base_rating": base_rating if base_rating is not None
else (sized.get("base_rating") if sized else app.base_match_rating(match_type)),
"valid_outcomes": list(sized["outcomes"]) if sized
else list(app.STANDARD_MATCH_OUTCOMES)}
def test_validate_custom_game_accepts_and_normalizes_profiles(self):
saved = app.validate_custom_game(self.config())
self.assertEqual(saved["game_id"], "custom-fantasy-fed-rumble-royale")
self.assertEqual(saved["schema_version"], 1)
self.assertEqual(saved["match_types"][0]["base_rating"], 8.5)
self.assertEqual(saved["match_types"][1]["base_rating"],
app.MATCH_BASE_RATINGS["Tag Team"])
self.assertEqual(saved["match_types"][0]["outcomes"],
list(app.STANDARD_MATCH_OUTCOMES))
self.assertEqual(saved["match_types"][1]["outcomes"], ["pinfall", "submission"])
self.assertEqual(saved["championships"][2]["kind"], "women")
self.assertIsNone(saved["screenshot_directory"])
saved_with_override = app.validate_custom_game(
self.config(screenshot_directory=" ~/Game Shots ")
)
self.assertEqual(saved_with_override["screenshot_directory"], "~/Game Shots")
def test_validate_custom_game_rejects_invalid_definitions(self):
rejects = [
({"game_id": "sd1"}, "custom definitions cannot overwrite built-in games"),
({"game_id": "Bad ID"}, "game_id must use lowercase"),
({"roster": ["Ava Crusher", "ava crusher"]}, "unique"),
({"roster": ["Solo"]}, "at least two"),
({"women_roster": ["Not Listed"]}, "subset of the roster"),
({"match_types": [{"name": "Tag Tornado", "sides": [3, 3]}]},
"invalid side sizes"),
({"match_types": []}, "at least one match type"),
({"championships": [{"name": "Belt", "kind": "hardcore"}]}, "kind"),
({"light_heavyweight_roster": ["Not Listed"]}, "subset of the roster"),
({"platform": "dreamcast"}, "platform must be ps1"),
({"screenshot_directory": 3}, "screenshot_directory must be a path string"),
]
for overrides, message in rejects:
with self.subTest(overrides=overrides):
data = self.config(**overrides)
with self.assertRaises(ValueError) as caught:
app.validate_custom_game(data)
self.assertIn(message, str(caught.exception).lower())
def test_custom_games_use_platform_defaults_or_expanded_overrides(self):
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "CUSTOM_GAME_DIR", Path(folder) / "games"):
ps1 = self.config(game_id="custom-shot-ps1", platform="ps1")
ps2 = self.config(game_id="custom-shot-ps2", platform="ps2")
other = self.config(game_id="custom-shot-other", platform="other")
overridden = self.config(
game_id="custom-shot-override", platform="other",
screenshot_directory="~/GameScreens",
)
ps2_override = self.config(
game_id="custom-shot-ps2-override", platform="ps2",
screenshot_directory="~/PCSX2Alt",
)
relative = self.config(
game_id="custom-shot-relative", platform="other",
screenshot_directory="custom-screens",
)
for config in (ps1, ps2, other, overridden, ps2_override, relative):
app.save_custom_game(config, app.CUSTOM_GAME_DIR)
app.refresh_custom_games()
self.assertEqual(app.screenshot_directory_for_game(ps1["game_id"]),
app.DUCKSTATION_SCREENSHOT_DIR)
self.assertEqual(app.screenshot_directory_for_game(ps2["game_id"]),
app.PCSX2_SCREENSHOT_DIR)
self.assertIsNone(app.screenshot_directory_for_game(other["game_id"]))
self.assertFalse(app.game_has_screenshots(other["game_id"]))
self.assertEqual(app.screenshot_directory_for_game(overridden["game_id"]),
Path.home() / "GameScreens")
self.assertEqual(app.screenshot_directory_for_game(ps2_override["game_id"]),
Path.home() / "PCSX2Alt")
self.assertEqual(app.screenshot_directory_for_game(relative["game_id"]),
app.ROOT / "custom-screens")
self.assertTrue(app.game_has_screenshots(overridden["game_id"]))
def test_custom_light_heavyweight_eligibility_is_validated_and_applied(self):
config = self.config(
championships=[
{"name": "Fantasy Lightweight", "kind": "light_heavyweight"},
],
light_heavyweight_roster=["Ava Crusher", "Cara Storm"],
)
self.register(config)
profile = app.game_profile(config["game_id"])
self.assertEqual(
app.eligible_roster_for_division(
config["game_id"], "Fantasy Lightweight", profile=profile
),
["Ava Crusher", "Cara Storm"],
)
self.assertEqual(
app.championship_kind(config["game_id"], "Fantasy Lightweight", profile),
"light_heavyweight",
)
with self.assertRaisesRegex(ValueError, "must contain eligible wrestlers"):
app.validate_custom_game(self.config(championships=[
{"name": "Fantasy Lightweight", "kind": "light_heavyweight"},
]))
def test_light_heavyweight_eligibility_editor_selects_roster_members(self):
config = {"roster": ["Ava Crusher", "Ben Brawler"],
"light_heavyweight_roster": []}
with patch.object(app, "ask_choice", side_effect=[0, 0, 2]), \
contextlib.redirect_stdout(io.StringIO()):
app.edit_light_heavyweight_roster(config)
self.assertEqual(config["light_heavyweight_roster"], ["Ava Crusher"])
def test_custom_game_screenshot_override_can_be_set_in_editor(self):
config = app.validate_custom_game(self.config())
with patch.object(app, "ask_choice", side_effect=[0, 0, 6]), \
patch.object(app, "ask", side_effect=["Rumble Royale", "Fantasy Fed", "~/Shots"]), \
patch.object(app, "save_custom_game", side_effect=lambda value: value):
app.edit_custom_profile(config)
self.assertEqual(config["screenshot_directory"], "~/Shots")
def test_saved_custom_game_registers_in_catalog(self):
saved = self.register(self.config())
self.assertTrue((app.CUSTOM_GAME_DIR / f"{saved['game_id']}.json").is_file())
game_id = saved["game_id"]
self.assertIn(game_id, app.GAMES)
self.assertIn(game_id, app.CUSTOM_GAME_CONFIGS)
self.assertNotIn(game_id, app.BUILTIN_GAME_IDS)
self.assertEqual(app.load_roster(game_id), saved["roster"])
self.assertEqual(app.game_title(game_id), "Rumble Royale")
self.assertEqual(app.game_display_name(game_id),
"Fantasy Fed — Rumble Royale")
self.assertEqual(app.championship_names_for_game(game_id),
["Rumble Belt", "Fantasy Tag", "Fantasy Women"])
self.assertTrue(app.game_has_screenshots(game_id))
self.assertEqual(app.eligible_roster_for_division(game_id, "Fantasy Women"),
["Cara Storm"])
self.assertEqual([item["name"] for item in app.match_type_definitions(game_id)],
["Scaffold", "Tag Tornado"])
def test_custom_game_survives_relaunch_via_refresh(self):
self.register(self.config())
self.assertEqual(app.refresh_custom_games(),
["custom-fantasy-fed-rumble-royale"])
self.assertEqual(app.refresh_custom_games(),
["custom-fantasy-fed-rumble-royale"])
def test_custom_game_card_round_trip_keeps_game_metadata(self):
game_id = self.config()["game_id"]
self.register(self.config())
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "card.md"
card = {
"game_id": game_id, "show": "Rumble Night", "date": "2026-11-02",
"matches": [self.filler_match("Scaffold", base_rating=8.5),
self.filler_match("Tag Tornado"),
self.filler_match("Tag Tornado")],
"path": str(path),
}
app.save_card(card)
loaded = app.load_card(path)
self.assertEqual(loaded["game_title"], "Rumble Royale")
self.assertEqual(loaded["federation_name"], "Fantasy Fed")
self.assertEqual([item["name"] for item in loaded["title_definitions"]],
["Rumble Belt", "Fantasy Tag", "Fantasy Women"])
self.assertEqual(loaded["matches"][0]["type"], "Scaffold")
self.assertEqual(loaded["matches"][0]["base_rating"], 8.5)
self.assertEqual(loaded["matches"][1]["valid_outcomes"], ["pinfall", "submission"])
def test_custom_match_base_rating_can_drive_the_match_rating(self):
match = {"type": "Scaffold", "base_rating": 8.5, "match_time_minutes": 12.9,
"near_falls": 2, "finishers": 3, "title_defense": False}
plain = dict(match, base_rating=None)
self.assertEqual(app.calculate_match_rating(match) - app.calculate_match_rating(plain),
8.5 - app.base_match_rating("Scaffold"))
def test_custom_game_season_uses_custom_title_divisions(self):
config = self.config()
game_id = config["game_id"]
self.register(config)
season = app.new_season_state("Fantasy Season", game_id,
champions={"Rumble Belt": ["Ava Crusher"],
"Fantasy Tag": ["Ben Brawler", "Dash Vance"]},
season_id="fantasy-season")
self.assertEqual([item["name"] for item in season["title_definitions"]],
["Rumble Belt", "Fantasy Tag", "Fantasy Women"])
self.assertEqual(set(season["rankings"]),
{"Rumble Belt", "Fantasy Tag", "Fantasy Women"})
self.assertEqual(season["champions"]["Rumble Belt"]["holder"], ["Ava Crusher"])
with contextlib.redirect_stdout(io.StringIO()):
roster = app.load_roster(game_id)
matches = app.build_season_matches(season, "Raw")
self.assertEqual(len(matches), 3)
for match in matches:
self.assertIn(match["ranking_division"], (None, "Rumble Belt", "Fantasy Tag",
"Fantasy Women"))
# A custom division result updates the custom rankings.
match = {"type": "Scaffold", "sides": [["Ava Crusher"], ["Ben Brawler"]],
"winner_side": 0, "ranking_division": "Rumble Belt",
"title_defense": False}
app.record_season_match(season, {"season_show": "Raw"}, match, 0)
table = season["rankings"]["Rumble Belt"]
self.assertEqual(table[app.side_identity(["Ava Crusher"])]["wins"], 1)
self.assertEqual(table[app.side_identity(["Ben Brawler"])]["losses"], 1)
# A successful defense keeps the custom champion on top.
defense = {"type": "Scaffold", "sides": [["Ava Crusher"], ["Cara Storm"]],
"winner_side": 0, "ranking_division": "Rumble Belt",
"title_defense": True}
app.record_season_match(season, {"season_show": "Raw"}, defense, 0)
self.assertEqual(season["champions"]["Rumble Belt"]["holder"], ["Ava Crusher"])
self.assertEqual(season["champions"]["Rumble Belt"]["successful_defenses"], 1)
def test_custom_season_save_round_trip_keeps_title_definitions(self):
self.register(self.config())
season = app.new_season_state("Fantasy Season", self.config()["game_id"],
champions={"Rumble Belt": ["Ava Crusher"]},
season_id="fantasy-save")
with tempfile.TemporaryDirectory() as folder:
with patch.object(app, "SEASON_DIR", Path(folder)):
app.save_season(season)
loaded = app.load_season(season["season_id"])
self.assertEqual(loaded["game_title"], "Rumble Royale")
self.assertEqual(loaded["federation_name"], "Fantasy Fed")
self.assertEqual(loaded["champions"]["Rumble Belt"]["holder"], ["Ava Crusher"])
self.assertEqual([item["name"] for item in loaded["title_definitions"]],
["Rumble Belt", "Fantasy Tag", "Fantasy Women"])
self.assertEqual(loaded["shows"], app.DEFAULT_CUSTOM_SHOWS)
def test_choose_game_lists_builtin_games_then_custom_games(self):
self.register(self.config())
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", return_value=len(app.GAMES) - 1):
self.assertEqual(app.choose_game(), self.config()["game_id"])
CUSTOM_SHOWS = {"Raw": "Fantasy Rampage", "SmackDown": "Fantasy Clash",
"PPV": "Fantasy Super Card"}
def test_shows_validation_accepts_normalizes_and_rejects_bad_values(self):
saved = app.validate_custom_game(self.config(shows=self.CUSTOM_SHOWS))
self.assertEqual(saved["shows"], self.CUSTOM_SHOWS)
# Absent shows fall back to neutral placeholders, never WWE names.
self.assertEqual(app.validate_custom_game(self.config())["shows"],
app.DEFAULT_CUSTOM_SHOWS)
rejects = [
({"shows": ["Fantasy Rampage"]}, "shows must be an object"),
({"shows": {"Raw": " "}}, "non-empty string"),
({"shows": self.CUSTOM_SHOWS | {"Raw": "fantasy clash"}}, "unique"),
({"shows": {"Raw": 3}}, "non-empty string"),
]
for overrides, message in rejects:
with self.subTest(overrides=overrides):
data = self.config(**overrides)
with self.assertRaises(ValueError) as caught:
app.validate_custom_game(data)
self.assertIn(message, str(caught.exception).lower())
def test_game_show_names_resolve_wwe_names_only_for_builtins(self):
self.assertEqual(app.game_show_names("sd1")["SmackDown"],
"Thursday Night SmackDown!")
self.register(self.config())
self.assertEqual(app.game_show_names(self.config()["game_id"]),
app.DEFAULT_CUSTOM_SHOWS)
self.register(self.config(shows=self.CUSTOM_SHOWS))
game_id = self.config()["game_id"]
self.assertEqual(app.game_show_names(game_id), self.CUSTOM_SHOWS)
def test_built_in_clone_prefills_wwe_show_names(self):
profile = app.builtin_game_profile("sd2")
self.assertEqual(profile["shows"]["Raw"], "Monday Night Raw")
copied = app.builtin_profile_for_copy("sd2", "custom-clone-test")
self.assertEqual(copied["shows"], {"Raw": "Monday Night Raw",
"SmackDown": "Thursday Night SmackDown!",
"PPV": "WWF PPV"})
def test_booked_season_cards_use_custom_show_names(self):
game_id = self.register(self.config(shows=self.CUSTOM_SHOWS))["game_id"]
season = app.new_season_state("Show Name Season", game_id, champions={},
season_id="showname-season")
self.assertEqual(season["shows"], self.CUSTOM_SHOWS)
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "SEASON_DIR", Path(folder) / "seasons"), \
patch.object(app, "CARD_DIR", Path(folder) / "cards"), \
contextlib.redirect_stdout(io.StringIO()):
names = []
for show_number in (1, 2, 3):
season["next_show_number"] = show_number
card = app.book_next_season_show(season)
names.append(card["show"])
season["current_card"] = ""
card["matches"][0]["winner_side"] = 0
season_path = app.season_path(season["season_id"])
# The season save keeps its custom show-name snapshot on disk.
saved = json.loads(season_path.read_text("utf-8"))
self.assertEqual(saved["shows"], self.CUSTOM_SHOWS)
self.assertEqual(names, ["Fantasy Rampage", "Fantasy Clash", "Fantasy Super Card"])
def test_is_ppv_card_matches_custom_ppv_show_name_exactly(self):
self.register(self.config(shows=self.CUSTOM_SHOWS))
game_id = self.config()["game_id"]
event_card = {"game_id": game_id, "show": "Fantasy Super Card"}
weekly_card = {"game_id": game_id, "show": "Fantasy Rampage"}
self.assertTrue(app.is_ppv_card(event_card))
self.assertFalse(app.is_ppv_card(weekly_card))
# Case-insensitive exact match still applies.
self.assertTrue(app.is_ppv_card({"game_id": game_id, "show": "fantasy super card"}))
# Unknown card shows stay non-PPV even with a custom game.
self.assertFalse(app.is_ppv_card({"game_id": game_id, "show": "House Party"}))
def test_one_off_cards_use_game_show_names(self):
game_id = self.register(self.config(shows=self.CUSTOM_SHOWS))["game_id"]
with tempfile.TemporaryDirectory() as folder, \
patch.object(app, "CARD_DIR", Path(folder) / "cards"), \
contextlib.redirect_stdout(io.StringIO()):
with patch.object(app, "choose_game", return_value=game_id):
card = app.create_random_card()
self.assertIn(card["show"], set(self.CUSTOM_SHOWS.values()))
# The create-a-card show prompt defaults to the second weekly slot.
captured = {}
class Stop(Exception):
pass
def fake_ask(prompt, default=None):
captured["default"] = default
raise Stop()
with patch.object(app, "choose_game", return_value=game_id), \
patch.object(app, "ask", fake_ask), \
self.assertRaises(Stop):
app.create_card()
self.assertEqual(captured["default"], "Fantasy Clash")
def test_edit_custom_shows_renames_and_rejects_duplicates(self):
config = {"shows": dict(self.CUSTOM_SHOWS)}
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", side_effect=[0, 3]), \
patch.object(app, "ask", return_value="Fantasy Festival"):
app.edit_custom_shows(config)
self.assertEqual(config["shows"]["Raw"], "Fantasy Festival")
config = {"shows": dict(self.CUSTOM_SHOWS)}
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", side_effect=[1, 3]), \
patch.object(app, "ask", side_effect=["Fantasy Rampage", "Fantasy Zoom"]):
app.edit_custom_shows(config)
self.assertEqual(config["shows"]["SmackDown"], "Fantasy Zoom")
def test_view_roster_lists_names_read_only(self):
config = {"roster": ["Zulu Viper", "Ava Crusher", "Ben Brawler"],
"women_roster": ["Ava Crusher"]}
captured = io.StringIO()
with contextlib.redirect_stdout(captured), \
patch.object(app, "ask_choice", side_effect=[2, 3]):
app.edit_custom_roster(config)
text = captured.getvalue()
self.assertIn("Roster (3 wrestlers):", text)
self.assertIn(" 1. Ava Crusher", text)
self.assertIn(" 2. Ben Brawler", text)
self.assertIn(" 3. Zulu Viper", text)
self.assertEqual(config["roster"], ["Zulu Viper", "Ava Crusher", "Ben Brawler"])
self.assertEqual(config["women_roster"], ["Ava Crusher"])
def test_view_roster_on_empty_roster(self):
config = {"roster": [], "women_roster": []}
captured = io.StringIO()
with contextlib.redirect_stdout(captured), \
patch.object(app, "ask_choice", side_effect=[2, 3]):
app.edit_custom_roster(config)
self.assertIn("The roster is empty.", captured.getvalue())
def test_view_roster_dumps_large_roster_completely(self):
config = {"roster": [f"Wrestler {index:02d}" for index in range(65)],
"women_roster": []}
captured = io.StringIO()
with contextlib.redirect_stdout(captured), \
patch.object(app, "ask_choice", side_effect=[2, 3]):
app.edit_custom_roster(config)
text = captured.getvalue()
self.assertIn("Roster (65 wrestlers):", text)
self.assertIn(" 1. Wrestler 00", text)
self.assertIn(" 65. Wrestler 64", text)
def test_roster_menu_ordering_adds_view_between_remove_and_done(self):
seen = {}
config = {"roster": ["Ava Crusher"], "women_roster": []}
def capturing_choice(prompt, choices, default=None, labels=None):
if prompt == "Edit roster":
seen["labels"] = labels
return len(choices) - 1
with contextlib.redirect_stdout(io.StringIO()), \
patch.object(app, "ask_choice", side_effect=capturing_choice):
app.edit_custom_roster(config)
self.assertEqual(seen["labels"], ["Add a wrestler", "Remove a wrestler",
"View roster (read-only)", "Done (1 listed)"])
class FlashRenderingTests(unittest.TestCase):
def test_flash_updates_only_changed_rows_not_static_header(self):
card = MarkdownTests().make_card(Path("card.md"))
with patch.object(app, "_render_image", return_value=([], 0)):
normal = app.render_card_lines(card)
flashing = app.render_card_lines(card, flash=True)
header_length = len(app.header_lines(card, 100))
self.assertEqual(normal[:header_length], flashing[:header_length])
output = io.StringIO()
app.write_changed_lines(normal, flashing, stream=output)
updates = output.getvalue()
self.assertNotIn("WWF.com", updates)
self.assertNotIn("SMACKDOWN!", updates)
self.assertNotIn("\033[2J", updates)
self.assertIn("WIN!", updates)
self.assertIn("LOSE", updates)
def test_full_card_layout_fits_typical_terminal_height(self):
card = MarkdownTests().make_card(Path("card.md"))
card["matches"][2]["sides"] = [["Stone Cold Steve Austin"], ["Kane"]]
for match in card["matches"]:
match["promo"] = "First speaker: A short line.\nSecond: A reply.\nFirst: A close."
with (patch.object(app, "_render_image", side_effect=[(["logo"] * 6, 23), (["logo"] * 6, 38)]),
patch.object(app.shutil, "get_terminal_size", return_value=os.terminal_size((100, 24)))):
lines = app.render_card_lines(card)
self.assertLessEqual(len(lines), 24)
class HeaderLogoTests(unittest.TestCase):
def test_logo_selection_is_case_insensitive_and_show_dependent(self):
self.assertEqual(app.show_logo_path("Friday Night SmAcKdOwN"),
app.ROOT / "smackdown-logo-source.png")
self.assertEqual(app.show_logo_path("Monday Night RAW"),
app.ROOT / "raw-is-war-logo.png")
self.assertIsNone(app.show_logo_path("WrestleMania"))
def test_smackdown_takes_precedence_when_both_keywords_appear(self):
self.assertEqual(app.show_logo_path("Raw vs SmackDown"),
app.ROOT / "smackdown-logo-source.png")
def test_selected_logo_has_text_fallback_when_images_cannot_render(self):
card = MarkdownTests().make_card(Path("card.md"))
card["show"] = "Monday Night Raw"
with patch.object(app, "_render_image", return_value=([], 0)):
header = "\n".join(app.header_lines(card, 100))
self.assertIn("RAW IS WAR", header)
def custom_card(self) -> dict:
card = MarkdownTests().make_card(Path("card.md"))
card["federation_name"] = "Fantasy Fed"
card["game_title"] = "Rumble Royale"
return card
def test_is_wwf_federation_matches_canonical_name_and_alias(self):
for name in ("World Wrestling Federation", "world wrestling federation",
"WWF", " wwf "):
self.assertTrue(app.is_wwf_federation(name), name)
for name in ("Fantasy Fed", "WWE", "WCW", ""):
self.assertFalse(app.is_wwf_federation(name), name)
def test_corner_logo_only_renders_for_wwf_federations(self):
def image_mock(path, _columns, _rows):
if path.name == "wwfcomlogo.gif":
return ["IMG1"], 3
return ["C1"], 3
wwf_card = MarkdownTests().make_card(Path("card.md"))
wwf_card["show"] = "Monday Night Raw"
custom_card = self.custom_card()
custom_card["show"] = "Monday Night Raw"
with patch.object(app, "_render_image", side_effect=image_mock):
wwf_header = app.header_lines(wwf_card, 100)
custom_header = app.header_lines(custom_card, 100)
self.assertIn("IMG1", wwf_header[0])
self.assertFalse(any("IMG1" in line for line in custom_header))
# With no corner logo the centered show logo is truly centered (48 = (100-3)//2).
# With no corner logo the centered show logo is truly centered (48 = (100-3)//2)
# and the date still sits at the top-right of the same row.
self.assertTrue(custom_header[0].startswith(" " * 48 + "C1"), custom_header[0])
self.assertIn("2026-10-25", custom_header[0])
self.assertNotIn("WWF.com", "\n".join(app.header_lines(custom_card, 100)))
def test_wwf_alias_federations_render_the_corner_logo(self):
image_mock = lambda path, _columns, _rows: (["IMG1"], 3)
for federation in ("WWF", "world wrestling federation"):
card = self.custom_card()
card["federation_name"] = federation
with patch.object(app, "_render_image", side_effect=image_mock):
header = app.header_lines(card, 100)
self.assertIn("IMG1", header[0], federation)
def test_corner_logo_text_fallback_only_for_wwf_federations(self):
wwf_card = MarkdownTests().make_card(Path("card.md"))
custom_card = self.custom_card()
with patch.object(app, "_render_image", return_value=([], 0)):
wwf_header = "\n".join(app.header_lines(wwf_card, 100))
custom_header = "\n".join(app.header_lines(custom_card, 100))
self.assertIn("WWF.com", wwf_header)
self.assertNotIn("WWF.com", custom_header)
def test_narrow_custom_federation_header_drops_wwf_branding(self):
wwf_card = MarkdownTests().make_card(Path("card.md"))
custom_card = self.custom_card()
wwf_header = "\n".join(app.header_lines(wwf_card, 60))
custom_header = "\n".join(app.header_lines(custom_card, 60))
self.assertIn("WWF.com", wwf_header)
self.assertNotIn("WWF.com", custom_header)
self.assertIn("Rumble Royale", custom_header)
for header in (wwf_header, custom_header):
self.assertIn("2026-10-25", header)
class PromoFoldingTests(unittest.TestCase):
def card_with_promos(self) -> dict:
card = MarkdownTests().make_card(Path("card.md"))
card["matches"][2]["sides"] = [["Stone Cold Steve Austin"], ["Kane"]]
for match in card["matches"]:
match["promo"] = (
"The Rock: A deliberately long promo line that should wrap when expanded "
"instead of being clipped off at the edge of the terminal.\n"
"Jeff Hardy: A second complete response with enough detail to test folding.\n"
"The Rock: The final promo line remains available in the expanded view."
)
return card
def test_collapsed_promo_can_expand_to_all_wrapped_lines(self):
card = self.card_with_promos()
collapsed = app.match_lines(card["matches"][0], 1, 80, promo_selected=True)
expanded = app.match_lines(
card["matches"][0], 1, 80, promo_selected=True, promo_expanded=True
)
expanded_text = "\n".join(expanded)
self.assertTrue(any("▶ Promo:" in line for line in collapsed))
self.assertTrue(any("\033[7m" in line for line in collapsed))
self.assertTrue(any("▼ Promo (Enter to collapse)" in line for line in expanded))
self.assertIn("second complete response", expanded_text.lower())
self.assertIn("final promo line remains available", expanded_text)
self.assertGreater(len(expanded), len(collapsed))
def test_expanded_view_scrolls_body_but_keeps_header_pinned(self):
card = self.card_with_promos()
image_lines = ["logo"] * 6
image_mock = lambda path, _columns, _rows: (
image_lines, 23 if path.name == "wwfcomlogo.gif" else 38
)
with patch.object(app, "_render_image", side_effect=image_mock):
collapsed, _, _, _ = app.interactive_card_frame(card, 100, 24)
expanded, offset, capacity, spans = app.interactive_card_frame(
card, 100, 24, selected_promo=0, expanded_promos={0, 1, 2},
keep_selection_visible=True,
)
header = app.header_lines(card, 100)
self.assertEqual(len(expanded), 24)
self.assertGreater(offset, 0)
self.assertEqual(collapsed[:len(header)], expanded[:len(header)])
control_row, _ = spans[0]
view_row = len(header) + control_row - offset
self.assertIn("\033[7m", expanded[view_row])
self.assertGreaterEqual(capacity, 1)
def test_terminal_key_reader_maps_enter_arrows_and_page_keys(self):
for sequence, expected in ((b"\r", "enter"), (b"\x1b[A", "up"),
(b"\x1b[B", "down"), (b"\x1b[5~", "pageup"),
(b"\x1b[6~", "pagedown"), (b"q", "quit")):
read_fd, write_fd = os.pipe()
try:
os.write(write_fd, sequence)
self.assertEqual(app.read_view_key(read_fd), expected)
finally:
os.close(read_fd)
os.close(write_fd)
if __name__ == "__main__":
unittest.main()