Add TUI components for hosts management

- Implement DetailsHandler for managing entry details display and edit forms.
- Create EditHandler to handle edit mode operations, including validation and saving of entry changes.
- Introduce NavigationHandler for entry movement and action operations.
- Define key bindings for various application actions in keybindings.py.
- Add TableHandler for managing the data table, including sorting and filtering of entries.
- Establish CSS styles for consistent theming across the application.
- Update tests to reflect changes in module structure and ensure proper functionality.
This commit is contained in:
Philip Henning 2025-07-30 16:08:25 +02:00
parent 8b1c01c894
commit 4dbf200c5f
12 changed files with 2259 additions and 1029 deletions

View file

@ -8,7 +8,7 @@ validating application behavior, navigation, and user interactions.
from unittest.mock import Mock, patch
from hosts.main import HostsManagerApp
from hosts.tui.app import HostsManagerApp
from hosts.core.models import HostEntry, HostsFile
from hosts.core.parser import HostsParser
from hosts.core.config import Config
@ -19,7 +19,7 @@ class TestHostsManagerApp:
def test_app_initialization(self):
"""Test application initialization."""
with patch('hosts.main.HostsParser'), patch('hosts.main.Config'):
with patch('hosts.tui.app.HostsParser'), patch('hosts.tui.app.Config'):
app = HostsManagerApp()
assert app.title == "Hosts Manager"
@ -31,7 +31,7 @@ class TestHostsManagerApp:
def test_app_compose_method_exists(self):
"""Test that app has compose method."""
with patch('hosts.main.HostsParser'), patch('hosts.main.Config'):
with patch('hosts.tui.app.HostsParser'), patch('hosts.tui.app.Config'):
app = HostsManagerApp()
# Test that compose method exists and is callable
@ -55,8 +55,8 @@ class TestHostsManagerApp:
'size': 100
}
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.populate_entries_table = Mock()
@ -76,8 +76,8 @@ class TestHostsManagerApp:
mock_config = Mock(spec=Config)
mock_parser.parse.side_effect = FileNotFoundError("File not found")
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.update_status = Mock()
@ -93,8 +93,8 @@ class TestHostsManagerApp:
mock_config = Mock(spec=Config)
mock_parser.parse.side_effect = PermissionError("Permission denied")
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.update_status = Mock()
@ -111,8 +111,8 @@ class TestHostsManagerApp:
mock_config.should_show_default_entries.return_value = True
mock_config.is_default_entry.return_value = False
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -143,8 +143,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -176,8 +176,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -202,8 +202,8 @@ class TestHostsManagerApp:
'size': 100
}
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -234,8 +234,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -256,8 +256,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.load_hosts_file = Mock()
@ -273,8 +273,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.update_status = Mock()
@ -291,8 +291,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.push_screen = Mock()
@ -309,8 +309,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -339,8 +339,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -369,8 +369,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.update_entry_details = Mock()
@ -397,8 +397,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.action_sort_by_ip = Mock()
@ -419,8 +419,8 @@ class TestHostsManagerApp:
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
with patch('hosts.main.HostsParser', return_value=mock_parser), \
patch('hosts.main.Config', return_value=mock_config):
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
@ -449,7 +449,7 @@ class TestHostsManagerApp:
def test_app_bindings_defined(self):
"""Test that application has expected key bindings."""
with patch('hosts.main.HostsParser'), patch('hosts.main.Config'):
with patch('hosts.tui.app.HostsParser'), patch('hosts.tui.app.Config'):
app = HostsManagerApp()
# Check that bindings are defined