import forge from app import ( CSS, background_selector_html, refresh_screen, school_asset_name, school_selector_html, show_background, show_draft_from_rules, show_name, show_school, start_rules, ) from ui import new_run_shell class AppPackClient: # Return one deterministic draft pack for app-flow tests. def __init__(self) -> None: self.calls = 0 # Return a three-card model payload. def create_pack(self, payload): self.calls += 1 card = lambda index: { "name": f"Flow Card {self.calls}-{index}", "flavor": "The table waits.", "art_prompt": "glowing spell on a wooden desk", "effects": [{"primitive_id": "deal", "weight": 1}], } return {"cards": [card(index) for index in range(payload["pack_size"])]} class AppArtClient: # Count image requests made after card text is forged. def __init__(self) -> None: self.calls = 0 # Return a deterministic image URI. def create_art(self, prompt: str) -> str: self.calls += 1 return f"uri:{prompt}" # Verify setup advances through one prompt per screen. def test_setup_screen_steps() -> None: assert show_name()[1] == "name" assert show_background()[1] == "background" assert show_school()[1] == "school" # Verify the title CTA stays compact instead of full-width. def test_play_now_button_is_compact() -> None: assert "#play-now-btn" in CSS assert "max-width: 220px" in CSS assert "#start-draft-btn" in CSS assert "max-width: 200px" in CSS # Verify draft cards are compact enough for the draft table. def test_draft_cards_are_compact() -> None: assert "minmax(0, 300px)" in CSS assert "min-height: 405px" in CSS # Verify setup selectors are image-backed clickable panels with hover copy. def test_setup_selectors_use_image_panels() -> None: background = background_selector_html() school = school_selector_html() assert "selector-panel" in background assert "selector-image" in background assert "selector-copy" in background assert "background-btn-dark-fantasy" in background assert "You are a mage" in background assert "school-btn-fire" in school assert "school-btn-ice" in school assert "school-btn-earth" in school # Verify selector art resolves to the bundled user-provided filenames. def test_setup_selectors_use_bundled_assets() -> None: background = background_selector_html() assert "data:image/png;base64" in background assert school_asset_name("dark-fantasy", "fire") == "darkFantasyFire.png" assert school_asset_name("dark-fantasy", "ice") == "darkFantasyIce.png" assert school_asset_name("dark-fantasy", "earth") == "darkFantasyEarth.png" assert school_asset_name("cyberpunk", "fire") == "cyberpunkFire.png" assert school_asset_name("cyberpunk", "ice") == "cyberpunkice.png" assert school_asset_name("cyberpunk", "earth") == "cyberpunkEarth.png" assert school_asset_name("anime", "fire") == "animeFire.png" assert school_asset_name("anime", "ice") == "animeIce.png" assert school_asset_name("anime", "earth") == "animeEarth.png" assert "data:image/png;base64" in school_selector_html("Cyberpunk") # Verify the rules screen starts deck generation in the background. def test_start_rules_queues_deck_generation(monkeypatch) -> None: forge.reset() client = AppPackClient() monkeypatch.setattr("app.card_client_from_env", lambda: client) monkeypatch.setattr("app.art_client_from_env", lambda: None) output = start_rules("Ada", "ice", "Anime") state = output[0] # start_rules now lands on the boss reveal screen (rules follow on Continue). assert output[1] == "reveal" assert state.player_name == "Ada" assert state.school == "ice" assert "reveal-screen" in output[10] assert "The Necrolich" in output[10] forge.drain() refresh_screen(state, "rules") assert client.calls > 0 # Verify the rules screen starts image work once card text is ready. def test_rules_screen_starts_art_generation(monkeypatch) -> None: monkeypatch.setattr("ui.MIN_DRAFT_LOADING_SECONDS", 0.0) forge.reset() client = AppPackClient() art_client = AppArtClient() monkeypatch.setattr("app.card_client_from_env", lambda: client) monkeypatch.setattr("app.art_client_from_env", lambda: art_client) state = start_rules("Ada", "fire", "Cyberpunk")[0] forge.drain() state = refresh_screen(state, "rules")[0] forge.drain() refresh_screen(state, "rules") assert art_client.calls >= 3 # Verify skipping rules shows a deck-loading draft state if the pack is still pending. def test_skip_rules_shows_loading_deck(monkeypatch) -> None: forge.reset() monkeypatch.setattr("app.card_client_from_env", lambda: None) monkeypatch.setattr("app.art_client_from_env", lambda: None) state = new_run_shell("Ada", "Anime", "fire", seed=1) output = show_draft_from_rules(state) assert output[1] == "draft" assert output[0].loading == "Loading your deck" assert "Loading your deck" in output[12]