Spaces:
Running
Running
| 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] | |