Enhance status messages and layout in HostsManagerApp; improve error handling and testing for status updates

This commit is contained in:
Philip Henning 2025-07-30 00:18:48 +02:00
parent 02423fe4f2
commit d477328bea
3 changed files with 56 additions and 20 deletions

View file

@ -58,6 +58,8 @@
- ✅ **Safe file operations**: Atomic file writing with rollback capability - ✅ **Safe file operations**: Atomic file writing with rollback capability
- ✅ **Manager module**: Complete HostsManager class for edit operations - ✅ **Manager module**: Complete HostsManager class for edit operations
- ✅ **Error handling**: Comprehensive error handling with user feedback - ✅ **Error handling**: Comprehensive error handling with user feedback
- ✅ **Enhanced read-only error messages**: Clear, informative error messages with ❌ indicator and helpful instructions
- ✅ **Status bar positioning**: Fixed status bar layout to appear above footer for maximum visibility
- ✅ **Keyboard shortcuts**: All edit mode shortcuts implemented and tested - ✅ **Keyboard shortcuts**: All edit mode shortcuts implemented and tested
- ✅ **Live testing**: Manual testing confirms all functionality works correctly - ✅ **Live testing**: Manual testing confirms all functionality works correctly
- ✅ **Human-readable formatting**: Tab-based column alignment with proper spacing - ✅ **Human-readable formatting**: Tab-based column alignment with proper spacing
@ -109,7 +111,8 @@
5. ✅ **Entry reordering**: Move entries up/down with Ctrl+Up/Down keyboard shortcuts 5. ✅ **Entry reordering**: Move entries up/down with Ctrl+Up/Down keyboard shortcuts
6. ✅ **Manager module**: Complete HostsManager class for all edit operations 6. ✅ **Manager module**: Complete HostsManager class for all edit operations
7. ✅ **Safe file operations**: Atomic file writing with rollback capability 7. ✅ **Safe file operations**: Atomic file writing with rollback capability
8. ✅ **Comprehensive testing**: 38 new tests for manager module (135 total tests) 8. ✅ **Enhanced error messages**: Professional read-only mode error messages with clear instructions
9. ✅ **Comprehensive testing**: 38 new tests for manager module (135 total tests)
### Recent Major Accomplishments ### Recent Major Accomplishments
- ✅ **Complete Phase 3 implementation**: Full edit mode foundation with permission management - ✅ **Complete Phase 3 implementation**: Full edit mode foundation with permission management

View file

@ -28,7 +28,7 @@ class HostsManagerApp(App):
CSS = """ CSS = """
.hosts-container { .hosts-container {
height: 100%; height: 1fr;
} }
.left-pane { .left-pane {
@ -59,6 +59,16 @@ class HostsManagerApp(App):
color: $text; color: $text;
height: 1; height: 1;
padding: 0 1; padding: 0 1;
dock: bottom;
}
.status-error {
background: $error;
color: $text;
height: 1;
padding: 0 1;
text-style: bold;
dock: bottom;
} }
/* DataTable styling to match background */ /* DataTable styling to match background */
@ -115,20 +125,22 @@ class HostsManagerApp(App):
"""Create the application layout.""" """Create the application layout."""
yield Header() yield Header()
with Horizontal(classes="hosts-container"): with Vertical():
left_pane = Vertical(classes="left-pane") with Horizontal(classes="hosts-container"):
left_pane.border_title = "Hosts Entries" left_pane = Vertical(classes="left-pane")
with left_pane: left_pane.border_title = "Hosts Entries"
yield DataTable(id="entries-table") with left_pane:
yield left_pane yield DataTable(id="entries-table")
yield left_pane
right_pane = Vertical(classes="right-pane") right_pane = Vertical(classes="right-pane")
right_pane.border_title = "Entry Details" right_pane.border_title = "Entry Details"
with right_pane: with right_pane:
yield Static("", id="entry-details") yield Static("", id="entry-details")
yield right_pane yield right_pane
yield Static("", classes="status-bar", id="status")
yield Static("", classes="status-bar", id="status")
yield Footer() yield Footer()
def on_ready(self) -> None: def on_ready(self) -> None:
@ -367,8 +379,26 @@ class HostsManagerApp(App):
status_widget = self.query_one("#status", Static) status_widget = self.query_one("#status", Static)
if message: if message:
status_widget.update(message) # Check if this is an error message (starts with ❌)
if message.startswith(""):
# Use error styling for error messages
status_widget.remove_class("status-bar")
status_widget.add_class("status-error")
status_widget.update(message)
# Auto-clear error message after 5 seconds
self.set_timer(5.0, lambda: self.update_status())
else:
# Use normal styling for regular messages
status_widget.remove_class("status-error")
status_widget.add_class("status-bar")
status_widget.update(message)
# Auto-clear regular message after 3 seconds
self.set_timer(3.0, lambda: self.update_status())
else: else:
# Reset to normal status display
status_widget.remove_class("status-error")
status_widget.add_class("status-bar")
mode = "Edit mode" if self.edit_mode else "Read-only mode" mode = "Edit mode" if self.edit_mode else "Read-only mode"
entry_count = len(self.hosts_file.entries) entry_count = len(self.hosts_file.entries)
active_count = len(self.hosts_file.get_active_entries()) active_count = len(self.hosts_file.get_active_entries())
@ -485,7 +515,7 @@ class HostsManagerApp(App):
def action_toggle_entry(self) -> None: def action_toggle_entry(self) -> None:
"""Toggle the active state of the selected entry.""" """Toggle the active state of the selected entry."""
if not self.edit_mode: if not self.edit_mode:
self.update_status("Not in edit mode - press 'Ctrl+E' to enable editing") self.update_status("❌ Cannot toggle entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.")
return return
if not self.hosts_file.entries: if not self.hosts_file.entries:
@ -513,7 +543,7 @@ class HostsManagerApp(App):
def action_move_entry_up(self) -> None: def action_move_entry_up(self) -> None:
"""Move the selected entry up in the list.""" """Move the selected entry up in the list."""
if not self.edit_mode: if not self.edit_mode:
self.update_status("Not in edit mode - press 'Ctrl+E' to enable editing") self.update_status("❌ Cannot move entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.")
return return
if not self.hosts_file.entries: if not self.hosts_file.entries:
@ -544,7 +574,7 @@ class HostsManagerApp(App):
def action_move_entry_down(self) -> None: def action_move_entry_down(self) -> None:
"""Move the selected entry down in the list.""" """Move the selected entry down in the list."""
if not self.edit_mode: if not self.edit_mode:
self.update_status("Not in edit mode - press 'Ctrl+E' to enable editing") self.update_status("❌ Cannot move entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.")
return return
if not self.hosts_file.entries: if not self.hosts_file.entries:
@ -575,7 +605,7 @@ class HostsManagerApp(App):
def action_save_file(self) -> None: def action_save_file(self) -> None:
"""Save the hosts file to disk.""" """Save the hosts file to disk."""
if not self.edit_mode: if not self.edit_mode:
self.update_status("Not in edit mode - no changes to save") self.update_status("❌ Cannot save: Application is in read-only mode. No changes to save.")
return return
success, message = self.manager.save_hosts_file(self.hosts_file) success, message = self.manager.save_hosts_file(self.hosts_file)

View file

@ -243,14 +243,17 @@ class TestHostsManagerApp:
app = HostsManagerApp() app = HostsManagerApp()
# Mock the query_one method # Mock the query_one method and set_timer to avoid event loop issues
mock_status = Mock() mock_status = Mock()
app.query_one = Mock(return_value=mock_status) app.query_one = Mock(return_value=mock_status)
app.set_timer = Mock() # Mock the timer to avoid event loop issues
app.update_status("Custom status message") app.update_status("Custom status message")
# Verify status was updated with custom message # Verify status was updated with custom message
mock_status.update.assert_called_once_with("Custom status message") mock_status.update.assert_called_once_with("Custom status message")
# Verify timer was set for auto-clearing
app.set_timer.assert_called_once()
def test_action_reload(self): def test_action_reload(self):
"""Test reload action.""" """Test reload action."""