mirror of
https://github.com/RichieCahill/dotfiles.git
synced 2026-04-17 04:58:19 -04:00
Fix linter issues in test_splendor_human_widgets.py
https://claude.ai/code/session_01SVzgLDUS1Cdc4eh1ijETTh
This commit is contained in:
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ from python.splendor.base import (
|
|||||||
GameState,
|
GameState,
|
||||||
Noble,
|
Noble,
|
||||||
PlayerState,
|
PlayerState,
|
||||||
|
TakeDifferent,
|
||||||
create_random_cards,
|
create_random_cards,
|
||||||
create_random_nobles,
|
create_random_nobles,
|
||||||
new_game,
|
new_game,
|
||||||
@@ -64,23 +65,16 @@ async def test_board_compose_and_mount() -> None:
|
|||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
# Board should be mounted and its children present
|
|
||||||
board = app.query_one(Board)
|
board = app.query_one(Board)
|
||||||
assert board is not None
|
assert board is not None
|
||||||
|
|
||||||
# Verify sub-widgets exist
|
# Verify sub-widgets exist
|
||||||
bank_box = app.query_one("#bank_box")
|
assert app.query_one("#bank_box") is not None
|
||||||
assert bank_box is not None
|
assert app.query_one("#tier1_box") is not None
|
||||||
tier1 = app.query_one("#tier1_box")
|
assert app.query_one("#tier2_box") is not None
|
||||||
assert tier1 is not None
|
assert app.query_one("#tier3_box") is not None
|
||||||
tier2 = app.query_one("#tier2_box")
|
assert app.query_one("#nobles_box") is not None
|
||||||
assert tier2 is not None
|
assert app.query_one("#players_box") is not None
|
||||||
tier3 = app.query_one("#tier3_box")
|
|
||||||
assert tier3 is not None
|
|
||||||
nobles_box = app.query_one("#nobles_box")
|
|
||||||
assert nobles_box is not None
|
|
||||||
players_box = app.query_one("#players_box")
|
|
||||||
assert players_box is not None
|
|
||||||
|
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
@@ -94,7 +88,6 @@ async def test_board_render_bank() -> None:
|
|||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
board = app.query_one(Board)
|
board = app.query_one(Board)
|
||||||
# Call render explicitly to ensure it runs
|
|
||||||
board._render_bank()
|
board._render_bank()
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
@@ -117,7 +110,6 @@ async def test_board_render_tiers_empty() -> None:
|
|||||||
"""Board._render_tiers handles empty tiers."""
|
"""Board._render_tiers handles empty tiers."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
_patch_player_names(game)
|
_patch_player_names(game)
|
||||||
# Clear all table cards
|
|
||||||
for tier in game.table_by_tier:
|
for tier in game.table_by_tier:
|
||||||
game.table_by_tier[tier] = []
|
game.table_by_tier[tier] = []
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
@@ -174,14 +166,17 @@ async def test_board_render_players_with_nobles_and_cards() -> None:
|
|||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
_patch_player_names(game)
|
_patch_player_names(game)
|
||||||
p = game.players[0]
|
p = game.players[0]
|
||||||
# Give player some cards
|
card = Card(
|
||||||
card = Card(tier=1, points=1, color="white", cost=dict.fromkeys(GEM_COLORS, 0))
|
tier=1, points=1, color="white", cost=dict.fromkeys(GEM_COLORS, 0),
|
||||||
|
)
|
||||||
p.cards.append(card)
|
p.cards.append(card)
|
||||||
# Give player a reserved card
|
reserved = Card(
|
||||||
reserved = Card(tier=2, points=2, color="blue", cost=dict.fromkeys(GEM_COLORS, 0))
|
tier=2, points=2, color="blue", cost=dict.fromkeys(GEM_COLORS, 0),
|
||||||
|
)
|
||||||
p.reserved.append(reserved)
|
p.reserved.append(reserved)
|
||||||
# Give player a noble
|
noble = Noble(
|
||||||
noble = Noble(name="TestNoble", points=3, requirements=dict.fromkeys(GEM_COLORS, 0))
|
name="TestNoble", points=3, requirements=dict.fromkeys(GEM_COLORS, 0),
|
||||||
|
)
|
||||||
p.nobles.append(noble)
|
p.nobles.append(noble)
|
||||||
|
|
||||||
app = ActionApp(game, p)
|
app = ActionApp(game, p)
|
||||||
@@ -201,7 +196,6 @@ async def test_board_refresh_content() -> None:
|
|||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
board = app.query_one(Board)
|
board = app.query_one(Board)
|
||||||
# refresh_content should run without error (also called by on_mount)
|
|
||||||
board.refresh_content()
|
board.refresh_content()
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
@@ -219,17 +213,12 @@ async def test_action_app_compose_and_mount() -> None:
|
|||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
# Verify compose created the expected structure
|
from textual.widgets import Footer, Input, Static
|
||||||
from textual.widgets import Input, Footer, Static
|
|
||||||
|
|
||||||
input_w = app.query_one("#input_line", Input)
|
assert app.query_one("#input_line", Input) is not None
|
||||||
assert input_w is not None
|
assert app.query_one("#prompt", Static) is not None
|
||||||
prompt = app.query_one("#prompt", Static)
|
assert app.query_one("#board", Board) is not None
|
||||||
assert prompt is not None
|
assert app.query_one(Footer) is not None
|
||||||
board = app.query_one("#board", Board)
|
|
||||||
assert board is not None
|
|
||||||
footer = app.query_one(Footer)
|
|
||||||
assert footer is not None
|
|
||||||
|
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
@@ -259,99 +248,102 @@ async def test_action_app_update_prompt_with_message() -> None:
|
|||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def _make_mock_input_event(value: str):
|
@pytest.mark.asyncio
|
||||||
"""Create a mock Input.Submitted event."""
|
async def test_action_app_on_input_submitted_quit() -> None:
|
||||||
mock_event = MagicMock()
|
"""ActionApp exits on 'q' input via pilot keyboard."""
|
||||||
mock_event.value = value
|
|
||||||
mock_event.input = MagicMock()
|
|
||||||
mock_event.input.value = value
|
|
||||||
return mock_event
|
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_quit_sync() -> None:
|
|
||||||
"""ActionApp exits on 'q' input (sync test via direct method call)."""
|
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("q")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("q", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.result is None
|
assert app.result is None
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_quit_word_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_action_app_on_input_submitted_quit_word() -> None:
|
||||||
"""ActionApp exits on 'quit' input."""
|
"""ActionApp exits on 'quit' input."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("quit")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("q", "u", "i", "t", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.result is None
|
assert app.result is None
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_zero_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_action_app_on_input_submitted_zero() -> None:
|
||||||
"""ActionApp exits on '0' input."""
|
"""ActionApp exits on '0' input."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("0")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("0", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.result is None
|
assert app.result is None
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_empty_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_action_app_on_input_submitted_empty() -> None:
|
||||||
"""ActionApp ignores empty input."""
|
"""ActionApp ignores empty input."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("enter")
|
||||||
app.exit.assert_not_called()
|
await pilot.pause()
|
||||||
|
assert app.result is None
|
||||||
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_valid_cmd_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
"""ActionApp processes valid command '1 w b g'."""
|
async def test_action_app_on_input_submitted_valid_cmd() -> None:
|
||||||
|
"""ActionApp processes valid command '1 w b g' and exits."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("1 w b g")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
for ch in "1 w b g":
|
||||||
from python.splendor.base import TakeDifferent
|
await pilot.press(ch)
|
||||||
|
await pilot.press("enter")
|
||||||
|
await pilot.pause()
|
||||||
assert isinstance(app.result, TakeDifferent)
|
assert isinstance(app.result, TakeDifferent)
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_error_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_action_app_on_input_submitted_error() -> None:
|
||||||
"""ActionApp shows error message for bad command."""
|
"""ActionApp shows error message for bad command."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("badcmd")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
for ch in "xyz":
|
||||||
|
await pilot.press(ch)
|
||||||
|
await pilot.press("enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.message == "Unknown command."
|
assert app.message == "Unknown command."
|
||||||
app._update_prompt.assert_called_once()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_action_app_on_input_submitted_cmd_error_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_action_app_on_input_submitted_cmd_error() -> None:
|
||||||
"""ActionApp shows error from a valid command number but bad args."""
|
"""ActionApp shows error from a valid command number but bad args."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
app = ActionApp(game, game.players[0])
|
app = ActionApp(game, game.players[0])
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("1")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("1", "enter")
|
||||||
assert "color" in app.message.lower() or "Need" in app.message
|
await pilot.pause()
|
||||||
|
assert app.message != ""
|
||||||
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -359,20 +351,25 @@ def test_action_app_on_input_submitted_cmd_error_sync() -> None:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _make_discard_game(excess: int = 1):
|
||||||
|
"""Create a game where player 0 has excess tokens over the limit."""
|
||||||
|
game, _bots = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
|
p = game.players[0]
|
||||||
|
for c in GEM_COLORS:
|
||||||
|
p.tokens[c] = 0
|
||||||
|
p.tokens["white"] = game.config.token_limit + excess
|
||||||
|
return game, p
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_discard_app_compose_and_mount() -> None:
|
async def test_discard_app_compose_and_mount() -> None:
|
||||||
"""DiscardApp composes header, command_zone, board, footer."""
|
"""DiscardApp composes header, command_zone, board, footer."""
|
||||||
game, _ = _make_game()
|
game, p = _make_discard_game(2)
|
||||||
_patch_player_names(game)
|
|
||||||
# Give player excess tokens so discard makes sense
|
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
from textual.widgets import Header, Footer, Input, Static
|
from textual.widgets import Footer, Header, Input, Static
|
||||||
|
|
||||||
assert app.query_one(Header) is not None
|
assert app.query_one(Header) is not None
|
||||||
assert app.query_one("#input_line", Input) is not None
|
assert app.query_one("#input_line", Input) is not None
|
||||||
@@ -386,12 +383,7 @@ async def test_discard_app_compose_and_mount() -> None:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_discard_app_update_prompt() -> None:
|
async def test_discard_app_update_prompt() -> None:
|
||||||
"""DiscardApp._update_prompt shows remaining discards info."""
|
"""DiscardApp._update_prompt shows remaining discards info."""
|
||||||
game, _ = _make_game()
|
game, p = _make_discard_game(2)
|
||||||
_patch_player_names(game)
|
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
@@ -402,12 +394,7 @@ async def test_discard_app_update_prompt() -> None:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_discard_app_update_prompt_with_message() -> None:
|
async def test_discard_app_update_prompt_with_message() -> None:
|
||||||
"""DiscardApp._update_prompt includes error message."""
|
"""DiscardApp._update_prompt includes error message."""
|
||||||
game, _ = _make_game()
|
game, p = _make_discard_game(2)
|
||||||
_patch_player_names(game)
|
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
@@ -419,114 +406,73 @@ async def test_discard_app_update_prompt_with_message() -> None:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_discard_app_on_input_submitted_empty() -> None:
|
async def test_discard_app_on_input_submitted_empty() -> None:
|
||||||
"""DiscardApp ignores empty input."""
|
"""DiscardApp ignores empty input."""
|
||||||
game, _ = _make_game()
|
game, p = _make_discard_game(2)
|
||||||
_patch_player_names(game)
|
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
input_w = app.query_one("#input_line")
|
await pilot.press("enter")
|
||||||
input_w.value = ""
|
await pilot.pause()
|
||||||
await input_w.action_submit()
|
|
||||||
# Nothing should change
|
|
||||||
assert all(v == 0 for v in app.discards.values())
|
assert all(v == 0 for v in app.discards.values())
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_discard_app_on_input_submitted_unknown_color_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_discard_app_on_input_submitted_unknown_color() -> None:
|
||||||
"""DiscardApp shows error for unknown color."""
|
"""DiscardApp shows error for unknown color."""
|
||||||
game, _ = _make_game()
|
game, p = _make_discard_game(2)
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("purple")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
for ch in "purple":
|
||||||
|
await pilot.press(ch)
|
||||||
|
await pilot.press("enter")
|
||||||
|
await pilot.pause()
|
||||||
assert "Unknown color" in app.message
|
assert "Unknown color" in app.message
|
||||||
app._update_prompt.assert_called()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_discard_app_on_input_submitted_no_tokens_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_discard_app_on_input_submitted_no_tokens() -> None:
|
||||||
"""DiscardApp shows error when no tokens of that color available."""
|
"""DiscardApp shows error when no tokens of that color available."""
|
||||||
game, _ = _make_game()
|
game, p = _make_discard_game(2)
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
p.tokens["white"] = 0
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("white")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
for ch in "blue":
|
||||||
|
await pilot.press(ch)
|
||||||
|
await pilot.press("enter")
|
||||||
|
await pilot.pause()
|
||||||
assert "No more" in app.message
|
assert "No more" in app.message
|
||||||
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_discard_app_on_input_submitted_valid_discard_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
"""DiscardApp increments discard count for valid color."""
|
async def test_discard_app_on_input_submitted_valid_finishes() -> None:
|
||||||
game, _ = _make_game()
|
"""DiscardApp increments discard and exits when done (excess=1)."""
|
||||||
p = game.players[0]
|
game, p = _make_discard_game(excess=1)
|
||||||
total_needed = game.config.token_limit + 1
|
|
||||||
p.tokens["white"] = total_needed
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
if c != "white":
|
|
||||||
p.tokens[c] = 0
|
|
||||||
p.tokens["gold"] = 0
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("white")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("w", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.discards["white"] == 1
|
assert app.discards["white"] == 1
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_discard_app_on_input_submitted_not_done_yet_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
"""DiscardApp stays open when more discards still needed."""
|
async def test_discard_app_on_input_submitted_not_done_yet() -> None:
|
||||||
game, _ = _make_game()
|
"""DiscardApp stays open when more discards still needed (excess=2)."""
|
||||||
p = game.players[0]
|
game, p = _make_discard_game(excess=2)
|
||||||
total_needed = game.config.token_limit + 2
|
|
||||||
p.tokens["white"] = total_needed
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
if c != "white":
|
|
||||||
p.tokens[c] = 0
|
|
||||||
p.tokens["gold"] = 0
|
|
||||||
app = DiscardApp(game, p)
|
app = DiscardApp(game, p)
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("white")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("w", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.discards["white"] == 1
|
assert app.discards["white"] == 1
|
||||||
assert app.message == ""
|
assert app.message == ""
|
||||||
app.exit.assert_not_called()
|
|
||||||
|
|
||||||
event2 = _make_mock_input_event("white")
|
await pilot.press("w", "enter")
|
||||||
app.on_input_submitted(event2)
|
await pilot.pause()
|
||||||
assert app.discards["white"] == 2
|
assert app.discards["white"] == 2
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_discard_app_on_input_submitted_empty_sync() -> None:
|
|
||||||
"""DiscardApp ignores empty input."""
|
|
||||||
game, _ = _make_game()
|
|
||||||
p = game.players[0]
|
|
||||||
for c in BASE_COLORS:
|
|
||||||
p.tokens[c] = 5
|
|
||||||
app = DiscardApp(game, p)
|
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("")
|
|
||||||
app.on_input_submitted(event)
|
|
||||||
assert all(v == 0 for v in app.discards.values())
|
|
||||||
app.exit.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -543,7 +489,7 @@ async def test_noble_choice_app_compose_and_mount() -> None:
|
|||||||
app = NobleChoiceApp(game, game.players[0], nobles)
|
app = NobleChoiceApp(game, game.players[0], nobles)
|
||||||
|
|
||||||
async with app.run_test() as pilot:
|
async with app.run_test() as pilot:
|
||||||
from textual.widgets import Header, Footer, Input, Static
|
from textual.widgets import Footer, Header, Input, Static
|
||||||
|
|
||||||
assert app.query_one(Header) is not None
|
assert app.query_one(Header) is not None
|
||||||
assert app.query_one("#input_line", Input) is not None
|
assert app.query_one("#input_line", Input) is not None
|
||||||
@@ -581,70 +527,79 @@ async def test_noble_choice_app_update_prompt_with_message() -> None:
|
|||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_noble_choice_app_on_input_submitted_empty_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_noble_choice_app_on_input_submitted_empty() -> None:
|
||||||
"""NobleChoiceApp ignores empty input."""
|
"""NobleChoiceApp ignores empty input."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
nobles = game.available_nobles[:2]
|
nobles = game.available_nobles[:2]
|
||||||
app = NobleChoiceApp(game, game.players[0], nobles)
|
app = NobleChoiceApp(game, game.players[0], nobles)
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.result is None
|
assert app.result is None
|
||||||
app.exit.assert_not_called()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_noble_choice_app_on_input_submitted_not_int_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_noble_choice_app_on_input_submitted_not_int() -> None:
|
||||||
"""NobleChoiceApp shows error for non-integer input."""
|
"""NobleChoiceApp shows error for non-integer input."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
nobles = game.available_nobles[:2]
|
nobles = game.available_nobles[:2]
|
||||||
app = NobleChoiceApp(game, game.players[0], nobles)
|
app = NobleChoiceApp(game, game.players[0], nobles)
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("abc")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
for ch in "abc":
|
||||||
|
await pilot.press(ch)
|
||||||
|
await pilot.press("enter")
|
||||||
|
await pilot.pause()
|
||||||
assert "valid integer" in app.message
|
assert "valid integer" in app.message
|
||||||
app._update_prompt.assert_called()
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_noble_choice_app_on_input_submitted_out_of_range_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_noble_choice_app_on_input_submitted_out_of_range() -> None:
|
||||||
"""NobleChoiceApp shows error for index out of range."""
|
"""NobleChoiceApp shows error for index out of range."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
nobles = game.available_nobles[:2]
|
nobles = game.available_nobles[:2]
|
||||||
app = NobleChoiceApp(game, game.players[0], nobles)
|
app = NobleChoiceApp(game, game.players[0], nobles)
|
||||||
app.exit = MagicMock()
|
|
||||||
app._update_prompt = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("99")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("9", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert "out of range" in app.message.lower()
|
assert "out of range" in app.message.lower()
|
||||||
|
app.exit()
|
||||||
|
|
||||||
|
|
||||||
def test_noble_choice_app_on_input_submitted_valid_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_noble_choice_app_on_input_submitted_valid() -> None:
|
||||||
"""NobleChoiceApp selects noble and exits on valid index."""
|
"""NobleChoiceApp selects noble and exits on valid index."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
nobles = game.available_nobles[:2]
|
nobles = game.available_nobles[:2]
|
||||||
app = NobleChoiceApp(game, game.players[0], nobles)
|
app = NobleChoiceApp(game, game.players[0], nobles)
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("0")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("0", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.result is nobles[0]
|
assert app.result is nobles[0]
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_noble_choice_app_on_input_submitted_second_noble_sync() -> None:
|
@pytest.mark.asyncio
|
||||||
|
async def test_noble_choice_app_on_input_submitted_second_noble() -> None:
|
||||||
"""NobleChoiceApp selects second noble."""
|
"""NobleChoiceApp selects second noble."""
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
|
_patch_player_names(game)
|
||||||
nobles = game.available_nobles[:2]
|
nobles = game.available_nobles[:2]
|
||||||
app = NobleChoiceApp(game, game.players[0], nobles)
|
app = NobleChoiceApp(game, game.players[0], nobles)
|
||||||
app.exit = MagicMock()
|
|
||||||
|
|
||||||
event = _make_mock_input_event("1")
|
async with app.run_test() as pilot:
|
||||||
app.on_input_submitted(event)
|
await pilot.press("1", "enter")
|
||||||
|
await pilot.pause()
|
||||||
assert app.result is nobles[1]
|
assert app.result is nobles[1]
|
||||||
app.exit.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -653,25 +608,20 @@ def test_noble_choice_app_on_input_submitted_second_noble_sync() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_tui_human_choose_action_tty() -> None:
|
def test_tui_human_choose_action_tty() -> None:
|
||||||
"""TuiHuman.choose_action creates and runs ActionApp when stdout is a tty."""
|
"""TuiHuman.choose_action runs ActionApp when stdout is a tty."""
|
||||||
random.seed(42)
|
random.seed(42)
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
human = TuiHuman("test")
|
human = TuiHuman("test")
|
||||||
|
|
||||||
with patch.object(sys.stdout, "isatty", return_value=True):
|
with patch.object(sys.stdout, "isatty", return_value=True):
|
||||||
with patch.object(ActionApp, "run") as mock_run:
|
with patch.object(ActionApp, "run") as mock_run:
|
||||||
# Simulate the app setting a result
|
|
||||||
def set_result():
|
|
||||||
pass # result stays None (quit)
|
|
||||||
|
|
||||||
mock_run.side_effect = set_result
|
|
||||||
result = human.choose_action(game, game.players[0])
|
result = human.choose_action(game, game.players[0])
|
||||||
mock_run.assert_called_once()
|
mock_run.assert_called_once()
|
||||||
assert result is None # default result is None
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
def test_tui_human_choose_discard_tty() -> None:
|
def test_tui_human_choose_discard_tty() -> None:
|
||||||
"""TuiHuman.choose_discard creates and runs DiscardApp when stdout is a tty."""
|
"""TuiHuman.choose_discard runs DiscardApp when stdout is a tty."""
|
||||||
random.seed(42)
|
random.seed(42)
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
human = TuiHuman("test")
|
human = TuiHuman("test")
|
||||||
@@ -680,12 +630,11 @@ def test_tui_human_choose_discard_tty() -> None:
|
|||||||
with patch.object(DiscardApp, "run") as mock_run:
|
with patch.object(DiscardApp, "run") as mock_run:
|
||||||
result = human.choose_discard(game, game.players[0], 2)
|
result = human.choose_discard(game, game.players[0], 2)
|
||||||
mock_run.assert_called_once()
|
mock_run.assert_called_once()
|
||||||
# Default discards are all zeros
|
|
||||||
assert result == dict.fromkeys(GEM_COLORS, 0)
|
assert result == dict.fromkeys(GEM_COLORS, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_tui_human_choose_noble_tty() -> None:
|
def test_tui_human_choose_noble_tty() -> None:
|
||||||
"""TuiHuman.choose_noble creates and runs NobleChoiceApp when stdout is a tty."""
|
"""TuiHuman.choose_noble runs NobleChoiceApp when stdout is a tty."""
|
||||||
random.seed(42)
|
random.seed(42)
|
||||||
game, _ = _make_game()
|
game, _ = _make_game()
|
||||||
nobles = game.available_nobles[:2]
|
nobles = game.available_nobles[:2]
|
||||||
@@ -695,5 +644,4 @@ def test_tui_human_choose_noble_tty() -> None:
|
|||||||
with patch.object(NobleChoiceApp, "run") as mock_run:
|
with patch.object(NobleChoiceApp, "run") as mock_run:
|
||||||
result = human.choose_noble(game, game.players[0], nobles)
|
result = human.choose_noble(game, game.players[0], nobles)
|
||||||
mock_run.assert_called_once()
|
mock_run.assert_called_once()
|
||||||
# Default result is None
|
|
||||||
assert result is None
|
assert result is None
|
||||||
|
|||||||
Reference in New Issue
Block a user