This commit includes cleanup from model refactoring and terminal client
modularization for better code organization and maintainability.
## Game Models Refactor
**Removed RunnerState class:**
- Eliminated separate RunnerState model (was redundant)
- Replaced runners: List[RunnerState] with direct base references:
- on_first: Optional[LineupPlayerState]
- on_second: Optional[LineupPlayerState]
- on_third: Optional[LineupPlayerState]
- Updated helper methods:
- get_runner_at_base() now returns LineupPlayerState directly
- get_all_runners() returns List[Tuple[int, LineupPlayerState]]
- is_runner_on_X() simplified to direct None checks
**Benefits:**
- Matches database structure (plays table has on_first_id, etc.)
- Simpler state management (direct references vs list management)
- Better type safety (LineupPlayerState vs generic runner)
- Easier to work with in game engine logic
**Updated files:**
- app/models/game_models.py - Removed RunnerState, updated GameState
- app/core/play_resolver.py - Use get_all_runners() instead of state.runners
- app/core/validators.py - Updated runner access patterns
- tests/unit/models/test_game_models.py - Updated test assertions
- tests/unit/core/test_play_resolver.py - Updated test data
- tests/unit/core/test_validators.py - Updated test data
## Terminal Client Refactor
**Modularization (DRY principle):**
Created separate modules for better code organization:
1. **terminal_client/commands.py** (10,243 bytes)
- Shared command functions for game operations
- Used by both CLI (main.py) and REPL (repl.py)
- Functions: submit_defensive_decision, submit_offensive_decision,
resolve_play, quick_play_sequence
- Single source of truth for command logic
2. **terminal_client/arg_parser.py** (7,280 bytes)
- Centralized argument parsing and validation
- Handles defensive/offensive decision arguments
- Validates formats (alignment, depths, hold runners, steal attempts)
3. **terminal_client/completions.py** (10,357 bytes)
- TAB completion support for REPL mode
- Command completions, option completions, dynamic completions
- Game ID completions, defensive/offensive option suggestions
4. **terminal_client/help_text.py** (10,839 bytes)
- Centralized help text and command documentation
- Detailed command descriptions
- Usage examples for all commands
**Updated main modules:**
- terminal_client/main.py - Simplified by using shared commands module
- terminal_client/repl.py - Cleaner with shared functions and completions
**Benefits:**
- DRY: Behavior consistent between CLI and REPL modes
- Maintainability: Changes in one place affect both interfaces
- Testability: Can test commands module independently
- Organization: Clear separation of concerns
## Documentation
**New files:**
- app/models/visual_model_relationships.md
- Visual documentation of model relationships
- Helps understand data flow between models
- terminal_client/update_docs/ (6 phase documentation files)
- Phased documentation for terminal client evolution
- Historical context for implementation decisions
## Tests
**New test files:**
- tests/unit/terminal_client/__init__.py
- tests/unit/terminal_client/test_arg_parser.py
- tests/unit/terminal_client/test_commands.py
- tests/unit/terminal_client/test_completions.py
- tests/unit/terminal_client/test_help_text.py
**Updated tests:**
- Integration tests updated for new runner model
- Unit tests updated for model changes
- All tests passing with new structure
## Summary
- ✅ Simplified game state model (removed RunnerState)
- ✅ Better alignment with database structure
- ✅ Modularized terminal client (DRY principle)
- ✅ Shared command logic between CLI and REPL
- ✅ Comprehensive test coverage
- ✅ Improved documentation
Total changes: 26 files modified/created
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
183 lines
6.3 KiB
Python
183 lines
6.3 KiB
Python
"""
|
|
Unit tests for help text system.
|
|
"""
|
|
import pytest
|
|
from unittest.mock import patch
|
|
|
|
from terminal_client.help_text import (
|
|
HelpFormatter,
|
|
get_help_text,
|
|
show_help,
|
|
HELP_DATA
|
|
)
|
|
|
|
|
|
class TestHelpData:
|
|
"""Tests for help data structure."""
|
|
|
|
def test_all_commands_have_help(self):
|
|
"""Test that all major commands have help data."""
|
|
required_commands = [
|
|
'new_game', 'defensive', 'offensive', 'resolve',
|
|
'quick_play', 'status', 'use_game', 'list_games'
|
|
]
|
|
|
|
for cmd in required_commands:
|
|
assert cmd in HELP_DATA, f"Missing help data for {cmd}"
|
|
|
|
def test_help_data_structure(self):
|
|
"""Test that help data has required fields."""
|
|
for cmd, data in HELP_DATA.items():
|
|
assert 'summary' in data, f"{cmd} missing summary"
|
|
assert 'usage' in data, f"{cmd} missing usage"
|
|
assert 'options' in data, f"{cmd} missing options"
|
|
assert 'examples' in data, f"{cmd} missing examples"
|
|
|
|
# Validate options structure
|
|
for opt in data['options']:
|
|
assert 'name' in opt, f"{cmd} option missing name"
|
|
assert 'desc' in opt, f"{cmd} option missing desc"
|
|
|
|
def test_help_data_has_examples(self):
|
|
"""Test that all commands have at least one example."""
|
|
for cmd, data in HELP_DATA.items():
|
|
assert len(data['examples']) > 0, f"{cmd} has no examples"
|
|
|
|
def test_get_help_text_valid(self):
|
|
"""Test getting help text for valid command."""
|
|
result = get_help_text('new_game')
|
|
assert result is not None
|
|
assert 'summary' in result
|
|
assert 'usage' in result
|
|
|
|
def test_get_help_text_invalid(self):
|
|
"""Test getting help text for invalid command."""
|
|
result = get_help_text('nonexistent_command')
|
|
assert result == {}
|
|
|
|
|
|
class TestHelpFormatter:
|
|
"""Tests for HelpFormatter."""
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_command_help(self, mock_console):
|
|
"""Test showing help for a command."""
|
|
help_data = {
|
|
'summary': 'Test command',
|
|
'usage': 'test [OPTIONS]',
|
|
'options': [
|
|
{'name': '--option', 'type': 'STRING', 'desc': 'Test option'}
|
|
],
|
|
'examples': ['test --option value']
|
|
}
|
|
|
|
HelpFormatter.show_command_help('test', help_data)
|
|
|
|
# Verify console.print was called
|
|
assert mock_console.print.called
|
|
assert mock_console.print.call_count >= 3 # Panel, options header, table, examples
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_command_help_with_notes(self, mock_console):
|
|
"""Test showing help with notes field."""
|
|
help_data = {
|
|
'summary': 'Test command',
|
|
'usage': 'test',
|
|
'options': [],
|
|
'examples': ['test'],
|
|
'notes': 'This is a test note'
|
|
}
|
|
|
|
HelpFormatter.show_command_help('test', help_data)
|
|
|
|
# Verify console.print was called
|
|
assert mock_console.print.called
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_command_list(self, mock_console):
|
|
"""Test showing command list."""
|
|
HelpFormatter.show_command_list()
|
|
|
|
# Verify console.print was called multiple times
|
|
# Should have: header, game management section, gameplay section, utilities section
|
|
assert mock_console.print.call_count > 5
|
|
|
|
|
|
class TestShowHelp:
|
|
"""Tests for show_help function."""
|
|
|
|
@patch('terminal_client.help_text.HelpFormatter.show_command_help')
|
|
def test_show_help_specific_command(self, mock_show):
|
|
"""Test showing help for specific command."""
|
|
show_help('new_game')
|
|
|
|
mock_show.assert_called_once()
|
|
args = mock_show.call_args[0]
|
|
assert args[0] == 'new_game'
|
|
assert 'summary' in args[1]
|
|
|
|
@patch('terminal_client.help_text.HelpFormatter.show_command_list')
|
|
def test_show_help_no_command(self, mock_list):
|
|
"""Test showing command list when no command specified."""
|
|
show_help()
|
|
|
|
mock_list.assert_called_once()
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_help_invalid_command(self, mock_console):
|
|
"""Test showing help for invalid command."""
|
|
show_help('invalid_command')
|
|
|
|
# Should print warning message
|
|
assert mock_console.print.called
|
|
call_args = str(mock_console.print.call_args_list)
|
|
assert 'No help available' in call_args
|
|
|
|
|
|
class TestSpecificCommandHelp:
|
|
"""Tests for specific command help data."""
|
|
|
|
def test_new_game_help_complete(self):
|
|
"""Test new_game help has all expected fields."""
|
|
data = get_help_text('new_game')
|
|
assert data['summary']
|
|
assert data['usage']
|
|
assert len(data['options']) == 3 # --league, --home-team, --away-team
|
|
assert len(data['examples']) >= 2
|
|
|
|
def test_defensive_help_complete(self):
|
|
"""Test defensive help has all expected fields."""
|
|
data = get_help_text('defensive')
|
|
assert data['summary']
|
|
assert data['usage']
|
|
assert len(data['options']) == 4 # --alignment, --infield, --outfield, --hold
|
|
assert len(data['examples']) >= 3
|
|
|
|
def test_offensive_help_complete(self):
|
|
"""Test offensive help has all expected fields."""
|
|
data = get_help_text('offensive')
|
|
assert data['summary']
|
|
assert data['usage']
|
|
assert len(data['options']) == 4 # --approach, --steal, --hit-run, --bunt
|
|
assert len(data['examples']) >= 3
|
|
|
|
def test_resolve_help_has_notes(self):
|
|
"""Test resolve help includes notes about requirements."""
|
|
data = get_help_text('resolve')
|
|
assert 'notes' in data
|
|
assert 'Both defensive and offensive decisions' in data['notes']
|
|
|
|
def test_quick_play_help_complete(self):
|
|
"""Test quick_play help has all expected fields."""
|
|
data = get_help_text('quick_play')
|
|
assert data['summary']
|
|
assert data['usage']
|
|
assert len(data['options']) == 1 # COUNT
|
|
assert len(data['examples']) >= 3
|
|
|
|
def test_use_game_help_mentions_tab_completion(self):
|
|
"""Test use_game help mentions tab completion."""
|
|
data = get_help_text('use_game')
|
|
examples = '\n'.join(data['examples'])
|
|
assert 'TAB' in examples or 'tab' in examples.lower()
|