Enhance status display and entry details in HostsManagerApp

- Updated header title to "/etc/hosts Manager" and modified subtitle format.
- Implemented a dedicated overlay status bar for error messages, ensuring no layout shifts.
- Refactored entry details display to use DataTable with labeled rows for improved consistency.
- Added CSS styles for the new status bar and DataTable.
- Created tests for status bar visibility and DataTable functionality, ensuring all tests pass.
This commit is contained in:
Philip Henning 2025-07-31 09:47:09 +02:00
parent 999b949f32
commit 25001042e5
11 changed files with 524 additions and 98 deletions

View file

@ -22,8 +22,8 @@ class TestHostsManagerApp:
with patch('hosts.tui.app.HostsParser'), patch('hosts.tui.app.Config'):
app = HostsManagerApp()
assert app.title == "Hosts Manager"
assert app.sub_title == "Read-only mode"
assert app.title == "/etc/hosts Manager"
assert app.sub_title == "" # Now set by update_status
assert app.edit_mode is False
assert app.selected_entry_index == 0
assert app.sort_column == ""
@ -142,15 +142,26 @@ class TestHostsManagerApp:
"""Test updating entry details pane."""
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
mock_config.should_show_default_entries.return_value = True
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
# Mock the query_one method
mock_details = Mock()
app.query_one = Mock(return_value=mock_details)
# Mock the query_one method to return DataTable mock
mock_details_table = Mock()
mock_details_table.columns = [] # Mock empty columns list
mock_edit_form = Mock()
def mock_query_one(selector, widget_type=None):
if selector == "#entry-details-table":
return mock_details_table
elif selector == "#entry-edit-form":
return mock_edit_form
return Mock()
app.query_one = mock_query_one
# Add test entry
app.hosts_file = HostsFile()
@ -164,12 +175,12 @@ class TestHostsManagerApp:
app.update_entry_details()
# Verify update was called with content containing entry details
mock_details.update.assert_called_once()
call_args = mock_details.update.call_args[0][0]
assert "127.0.0.1" in call_args
assert "localhost, local" in call_args
assert "Test comment" in call_args
# Verify DataTable operations were called
mock_details_table.remove_class.assert_called_with("hidden")
mock_edit_form.add_class.assert_called_with("hidden")
mock_details_table.clear.assert_called_once()
mock_details_table.add_column.assert_called()
mock_details_table.add_row.assert_called()
def test_update_entry_details_no_entries(self):
"""Test updating entry details with no entries."""
@ -181,16 +192,29 @@ class TestHostsManagerApp:
app = HostsManagerApp()
# Mock the query_one method
mock_details = Mock()
app.query_one = Mock(return_value=mock_details)
# Mock the query_one method to return DataTable mock
mock_details_table = Mock()
mock_details_table.columns = [] # Mock empty columns list
mock_edit_form = Mock()
def mock_query_one(selector, widget_type=None):
if selector == "#entry-details-table":
return mock_details_table
elif selector == "#entry-edit-form":
return mock_edit_form
return Mock()
app.query_one = mock_query_one
app.hosts_file = HostsFile()
app.update_entry_details()
# Verify update was called with "No entries loaded"
mock_details.update.assert_called_once_with("No entries loaded")
# Verify DataTable operations were called for empty state
mock_details_table.remove_class.assert_called_with("hidden")
mock_edit_form.add_class.assert_called_with("hidden")
mock_details_table.clear.assert_called_once()
mock_details_table.add_column.assert_called_with("Field", key="field")
mock_details_table.add_row.assert_called_with("No entries loaded")
def test_update_status_default(self):
"""Test status bar update with default information."""
@ -233,13 +257,24 @@ class TestHostsManagerApp:
app = HostsManagerApp()
# Mock set_timer to avoid event loop issues
app.set_timer = Mock() # Mock the timer to avoid event loop issues
# Mock set_timer and query_one to avoid event loop and UI issues
app.set_timer = Mock()
mock_status_bar = Mock()
app.query_one = Mock(return_value=mock_status_bar)
# Add test hosts_file for subtitle generation
app.hosts_file = HostsFile()
app.hosts_file.add_entry(HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]))
app.hosts_file.add_entry(HostEntry(ip_address="192.168.1.1", hostnames=["router"], is_active=False))
app.update_status("Custom status message")
# Verify sub_title was set with custom message
assert app.sub_title == "Custom status message"
# Verify status bar was updated with custom message
mock_status_bar.update.assert_called_with("Custom status message")
mock_status_bar.remove_class.assert_called_with("hidden")
# Verify subtitle shows current status (not the custom message)
assert "2 entries" in app.sub_title
assert "Read-only mode" in app.sub_title
# Verify timer was set for auto-clearing
app.set_timer.assert_called_once()