Compare commits

..

No commits in common. "220818c8d159a3ffc79b472f21bb9fbed2ece12d" and "116752135566e7732db1907ed7fdff159b5208db" have entirely different histories.

9 changed files with 156 additions and 291 deletions

View file

@ -1,44 +1,19 @@
# Ac### Status Appearance Enhancement ✅ COMPLETED
Successfully implemented the user's requested status display improvements with overlay fix:
**New Header Layout:**
- **Title**: Changed from "Hosts Manager" to "/etc/hosts Manager"
- **Subtitle**: Now shows "29 entries (6 active) | Read-only mode" format
- **Error Messages**: Moved to dedicated status bar below header as overlay
**Overlay Status Bar Implementation:**
- **Fixed layout shifting issue**: Status bar now appears as overlay without moving panes down
- **Corrected positioning**: Status bar appears below header as overlay using `dock: top`, `layer: overlay`, `offset-y: 3`
- **Visible error messages**: Error messages now display correctly as overlay on content area
- **No layout flow impact**: Panes stay in exact same position when error messages appear
- **Professional appearance**: Error bar overlays cleanly below header without disrupting content layout
**Implementation Details:**
- Moved status bar widget to end of compose method for overlay rendering
- Status bar positioned 3 lines down from top (below header) using CSS offset
- Status bar is hidden by default, only appears when displaying messages
- Error messages (❌) auto-clear after 5 seconds, regular messages after 3 seconds
- Header subtitle always shows current status regardless of temporary messages
**Test Updates:**
- All 149 tests passing with overlay status bar implementation
- Fixed layout shifting that was annoying when error messages appeared
- Verified functionality maintains all previous behaviorive Context: hosts
# Active Context: hosts
## Current Work Focus
**Status Appearance Enhancement Complete**: Successfully implemented the user's requested status display improvements. The header now shows "/etc/hosts Manager" with entry counts and mode on the right, while error messages appear in a dedicated status bar below the header. Ready to proceed with remaining UX improvements from todo.md.
**Post-Phase 3 Code Quality Maintenance**: The hosts TUI application has successfully completed Phase 3 with full edit mode foundation, save confirmation functionality, and comprehensive testing (149 tests). However, 20 minor linting issues (unused imports and variables) require cleanup before proceeding to Phase 4 advanced features.
## Immediate Next Steps
### Priority 1: Remaining User Experience Improvements (From todo.md)
1. **Status appearance enhancement**: COMPLETED - New header layout with separate error message bar
2. **Entry details consistency**: COMPLETED - DataTable with labeled rows matching edit form order
3. **DataTable details implementation**: COMPLETED as part of entry details consistency
4. **Sudo permission fixes**: Address known sudo handling issues
### Priority 1: Code Quality Cleanup
1. **Fix linting issues**: Run `uv run ruff check --fix` to address 20 unused import and variable warnings
2. **Validate fixes**: Ensure all tests still pass (149 tests) after cleanup
3. **Confirm application functionality**: Test that `uv run hosts` still works perfectly
4. **Commit clean state**: Create commit with "Fix linting issues" once cleanup is complete
### Priority 2: Phase 4 Planning
Once remaining UX improvements are complete:
Once code quality is restored:
1. **Advanced entry operations**: Add/delete entries with validation
2. **Search functionality**: Find entries by hostname or IP address
3. **Bulk operations**: Select and modify multiple entries
@ -47,26 +22,27 @@ Once remaining UX improvements are complete:
## Memory Bank Update Summary
### Files Updated
- ✅ **activeContext.md**: Updated current focus to test stabilization and UX improvements
- ✅ **progress.md**: Corrected test status (8 failures out of 149) and development stage
- ✅ **techContext.md**: Updated development workflow and test status
- ✅ **projectbrief.md**: Noted current test failures in testing strategy
- ✅ **Added todo.md insights**: Documented user experience improvement requirements
- ✅ **activeContext.md**: Updated current focus and next steps
- ✅ **progress.md**: Corrected test count (149 vs 97), added code quality status
- ✅ **techContext.md**: Updated development workflow and code quality status
- ✅ **systemPatterns.md**: Added edit mode and permission management patterns
- ✅ **projectbrief.md**: Updated test coverage details and current status
### Current Status Corrections
- **Linting status**: Corrected to show clean state (all checks passing)
- **Test status**: Updated to reflect 8 failing tests out of 149 total
- **Application functionality**: Confirmed working TUI with identified improvement areas
- **Development priority**: Shifted from code cleanup to test stabilization
- **User requirements**: Added todo.md requirements for status, details, and sudo improvements
### Key Corrections Made
- **Test count**: Updated from 97 to 149 tests across all files
- **Code quality**: Noted 20 linting issues requiring cleanup
- **Project stage**: Clarified completion of Phase 3 with save confirmation
- **Current status**: Maintenance phase before Phase 4 development
- **Recent commits**: Reflected completion of save confirmation modal
### New Requirements from todo.md
1. **Status appearance enhancement**: Visual design improvements needed
2. **Entry details consistency**: Non-edit view should match edit mode field order
3. **DataTable details view**: Implement labeled rows for better presentation
4. **Sudo issue resolution**: Address known permission handling problems
### Architecture Insights Confirmed
- **Textual framework**: Excellent for complex TUI applications with modal dialogs
- **Layered architecture**: Proven effective for maintainable, testable code
- **Test-driven development**: 149 comprehensive tests enable confident refactoring
- **Configuration system**: JSON-based persistence working reliably
- **Permission management**: Sudo handling implemented safely and securely
The memory bank now accurately reflects the true current state: a functional application with clean code but test stability issues and identified user experience improvements needed before Phase 4 development.
The memory bank now accurately reflects the current state of the project, ready for the next phase of development after code quality maintenance.
## Recent Changes
@ -95,45 +71,22 @@ The memory bank now accurately reflects the true current state: a functional app
- **Professional visual design**: Color-coded entries, zebra striping, and rich text styling
- **Interactive sorting**: Click column headers or use keyboard shortcuts to sort data
- **Intelligent filtering**: Hide default system entries based on user preference
- **Comprehensive test coverage**: All 149 tests passing with 100% success rate
- **Clean code quality**: All ruff linting and formatting checks passing
- **Robust architecture**: Clean layered design ready for UX improvements and Phase 4 features
- **Todo requirements identified**: Status appearance, entry details consistency, sudo handling improvements needed
- **Comprehensive test coverage**: 149 tests with 100% pass rate covering all components
- **Code quality maintenance needed**: 20 linting issues (unused imports/variables) require cleanup
- **Robust architecture**: Clean layered design ready for Phase 4 advanced features
## Next Steps
### Entry Details Consistency ✅ COMPLETED
Successfully implemented DataTable-based entry details with consistent field ordering:
### Immediate Priority: Code Quality Cleanup
1. **Fix linting issues**: Address 20 unused import and variable warnings
- Remove unused imports in core/config.py, test files
- Clean up unused variables in exception handling
- Run `uv run ruff check --fix` to auto-fix issues
**Key Improvements:**
- **Replaced Static widget with DataTable**: Entry details now displayed in professional table format
- **Consistent field order**: Details view now matches edit form order exactly
1. IP Address
2. Hostnames (comma-separated)
3. Comment
4. Active status (Yes/No)
- **Labeled rows**: Uses DataTable labeled rows feature for clean presentation
- **No headers**: DataTable configured with `show_header=False` for clean appearance
**Implementation Details:**
- Modified `app.py` compose method to use DataTable instead of Static widget
- Updated `details_handler.py` to populate DataTable with labeled rows
- Added CSS styling for entry details table consistency
- Fixed 2 failing tests to work with new DataTable approach
- All 149 tests passing with new implementation
**Visual Benefits:**
- Professional table appearance matching main entries table
- Clear field labels in left column, values in right column
- Proper spacing and alignment
- System default entry warnings displayed in table format
- DNS Name field shown when present (read-only information)
### Priority 2: User Experience Improvements (From todo.md)
1. **Status appearance enhancement**: Improve visual design of status bar
2. **Entry details consistency**: Make non-edit view match edit mode field order
3. **DataTable details implementation**: Use labeled rows for better entry details display
4. **Sudo permission fixes**: Address known sudo handling issues
2. **Code quality validation**:
- Ensure all ruff checks pass with zero issues
- Maintain perfect test coverage (149 tests passing)
- Verify application functionality after cleanup
### Phase 4: Advanced Edit Features (Next Phase)
1. **Advanced editing operations**:

View file

@ -19,7 +19,7 @@
- ✅ **Entry management**: DataTable with proper formatting and status indicators
- ✅ **Detail view**: Comprehensive entry details in right pane
- ✅ **Navigation**: Smooth keyboard navigation with cursor position restoration
- ✅ **Testing**: 149 comprehensive tests with 100% pass rate (test stabilization completed)
- ✅ **Testing**: 97 comprehensive tests with 100% pass rate
- ✅ **Code quality**: All ruff linting and formatting checks passing
- ✅ **Error handling**: Graceful handling of file access and parsing errors
- ✅ **Status feedback**: Informative status bar with file and entry information
@ -67,13 +67,12 @@
## What's Left to Build
### Priority 1: User Experience Improvements (From todo.md)
- ✅ **Status appearance**: Enhanced visual design with new header layout and dedicated error message bar
### Priority 1: User Experience Improvements (From todo.md)
- ✅ **Status appearance**: Enhanced visual design with new header layout and separate error message bar
- ✅ **Entry details consistency**: Implemented DataTable with labeled rows matching edit form field order
- ✅ **DataTable details view**: Completed as part of entry details consistency improvement
- ❌ **Sudo permission handling**: Address known sudo issues### Phase 4: Advanced Edit Features
### Immediate Priority: Code Quality Cleanup
- ❌ **Fix linting issues**: Address 20 unused import and variable warnings
- ❌ **Code quality validation**: Ensure all ruff checks pass with zero issues
- ❌ **Maintain test coverage**: Keep 149 tests passing during cleanup
### Phase 4: Advanced Edit Features
- ❌ **Add new entries**: Create new host entries
- ❌ **Delete entries**: Remove host entries
- ❌ **Bulk operations**: Select and modify multiple entries
@ -96,10 +95,9 @@
## Current Status
### Development Stage
**Stage**: User Experience Improvements - 3 of 4 Todo Items Complete
**Progress**: 90% (Status improvements and entry details consistency completed, ready for final sudo fixes and Phase 4)
**Next Milestone**: Sudo permission handling fixes, then Phase 4 advanced features
**Test Status**: ✅ All 149 tests passing (maintained during UX improvements)
**Stage**: Phase 3 Complete with Code Quality Maintenance Required
**Progress**: 82% (Complete edit mode foundation with save confirmation, code cleanup needed)
**Next Milestone**: Code quality cleanup, then Phase 4 advanced edit features
### Phase 3 Final Achievements ✅ COMPLETE
1. ✅ **Permission management**: Complete PermissionManager class with sudo request and validation

View file

@ -81,7 +81,7 @@ hosts/
- Mock `/etc/hosts` file I/O and DNS lookups to avoid system dependencies.
- Include integration tests for the Textual TUI (using `textual.testing` or snapshot testing).
### Implemented Tests (149 tests total, 8 failing)
### Implemented Tests (149 tests total)
1. **Parsing Tests** (15 tests):
- Parse simple `/etc/hosts` with comments and disabled entries

View file

@ -32,14 +32,13 @@ hosts/
### Current State
- ✅ **Complete uv project**: Python 3.13 with full dependency management
- ✅ **Production application**: Fully functional TUI with advanced features and professional interface
- ✅ **Clean code quality**: All ruff linting and formatting checks passing
- ✅ **Code quality maintenance required**: 20 linting issues (unused imports/variables) need cleanup
- ✅ **Proper project structure**: Well-organized src/hosts/ package with core and tui modules
- ✅ **Test coverage restored**: All 149 tests passing after successful test stabilization
- ✅ **Comprehensive testing**: 149 tests covering all functionality including new features
- ✅ **Entry point configured**: `hosts` command launches application perfectly
- ✅ **Configuration system**: Complete settings management with JSON persistence
- ✅ **Modal interface**: Professional configuration and save confirmation dialogs
- ✅ **Advanced features**: Sorting, filtering, edit mode, and comprehensive TUI functionality
- ❌ **Known improvements needed**: Status appearance, entry details consistency, sudo handling (per todo.md)
- ✅ **Modal interface**: Professional configuration dialogs with keyboard bindings
- ✅ **Advanced features**: Sorting, filtering, edit mode, and save confirmation
### Runtime Management
- ✅ **uv run hosts**: Command executes application instantly
@ -95,9 +94,9 @@ hosts = "hosts.main:main"
### Development Workflow
1. ✅ **uv run hosts**: Execute the application - launches instantly
2. **uv run ruff check**: Lint code - all checks currently passing
2. 🔧 **uv run ruff check**: Lint code - 20 issues need fixing
3. ✅ **uv run ruff format**: Auto-format code - consistent style maintained
4. ✅ **uv run pytest**: Run test suite - All 149 tests passing with 100% success rate (test stabilization completed)
4. ✅ **uv run pytest**: Run test suite - 149 tests passing with 100% success rate
5. ✅ **uv add**: Add dependencies - seamless dependency management
### Code Quality Status

View file

@ -44,8 +44,8 @@ class HostsManagerApp(App):
def __init__(self):
super().__init__()
self.title = "/etc/hosts Manager"
self.sub_title = "" # Will be set by update_status
self.title = "Hosts Manager"
self.sub_title = "Read-only mode"
# Initialize core components
self.parser = HostsParser()
@ -75,7 +75,7 @@ class HostsManagerApp(App):
# Right pane - entry details or edit form
with Vertical(classes="right-pane"):
yield Static("Entry Details", id="details-title")
yield DataTable(id="entry-details-table", show_header=False, show_cursor=False, disabled=True)
yield Static("Select an entry to view details", id="entry-details")
# Edit form (initially hidden)
with Vertical(id="entry-edit-form", classes="hidden"):
@ -87,9 +87,6 @@ class HostsManagerApp(App):
yield Input(placeholder="Enter comment (optional)", id="comment-input")
yield Checkbox("Active", id="active-checkbox")
# Status bar for error/temporary messages (overlay, doesn't affect layout)
yield Static("", id="status-bar", classes="status-bar hidden")
def on_ready(self) -> None:
"""Called when the app is ready."""
self.load_hosts_file()
@ -114,40 +111,30 @@ class HostsManagerApp(App):
self.update_status(f"❌ Error loading hosts file: {e}")
def update_status(self, message: str = "") -> None:
"""Update the header subtitle and status bar with status information."""
"""Update the footer subtitle with status information."""
if message:
# Show temporary message in the status bar
try:
status_bar = self.query_one("#status-bar", Static)
status_bar.update(message)
status_bar.remove_class("hidden")
if message.startswith(""):
# Auto-clear error message after 5 seconds
self.set_timer(5.0, lambda: self._clear_status_message())
else:
# Auto-clear regular message after 3 seconds
self.set_timer(3.0, lambda: self._clear_status_message())
except:
# Fallback if status bar not found (during initialization)
pass
# Always update the header subtitle with current status
mode = "Edit mode" if self.edit_mode else "Read-only mode"
entry_count = len(self.hosts_file.entries)
active_count = len(self.hosts_file.get_active_entries())
# Set temporary status message
self.sub_title = message
if message.startswith(""):
# Auto-clear error message after 5 seconds
self.set_timer(5.0, lambda: self.update_status())
else:
# Auto-clear regular message after 3 seconds
self.set_timer(3.0, lambda: self.update_status())
else:
# Reset to normal status display
mode = "Edit mode" if self.edit_mode else "Read-only mode"
entry_count = len(self.hosts_file.entries)
active_count = len(self.hosts_file.get_active_entries())
# Format: "29 entries (6 active) | Read-only mode"
self.sub_title = f"{entry_count} entries ({active_count} active) | {mode}"
status_text = f"{mode} | {entry_count} entries ({active_count} active)"
def _clear_status_message(self) -> None:
"""Clear the temporary status message."""
try:
status_bar = self.query_one("#status-bar", Static)
status_bar.update("")
status_bar.add_class("hidden")
except:
pass
# Add file info
file_info = self.parser.get_file_info()
if file_info["exists"]:
status_text += f" | {file_info['path']}"
self.sub_title = status_text
# Event handlers
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:

View file

@ -5,7 +5,7 @@ This module handles the display and updating of entry details
and edit forms in the right pane.
"""
from textual.widgets import Static, Input, Checkbox, DataTable
from textual.widgets import Static, Input, Checkbox
class DetailsHandler:
@ -23,30 +23,22 @@ class DetailsHandler:
self.update_details_display()
def update_details_display(self) -> None:
"""Update the details display using a DataTable with labeled rows."""
details_table = self.app.query_one("#entry-details-table", DataTable)
"""Update the static details display."""
details_widget = self.app.query_one("#entry-details", Static)
edit_form = self.app.query_one("#entry-edit-form")
# Show details table, hide edit form
details_table.remove_class("hidden")
# Show details, hide edit form
details_widget.remove_class("hidden")
edit_form.add_class("hidden")
# Clear existing data
details_table.clear()
if not self.app.hosts_file.entries:
# Show empty message in a single row
if not details_table.columns:
details_table.add_column("Field", key="field")
details_table.add_row("No entries loaded")
details_widget.update("No entries loaded")
return
# Get visible entries to check if we need to adjust selection
visible_entries = self.app.table_handler.get_visible_entries()
if not visible_entries:
if not details_table.columns:
details_table.add_column("Field", key="field")
details_table.add_row("No visible entries")
details_widget.update("No visible entries")
return
# If default entries are hidden and selected_entry_index points to a hidden entry,
@ -73,34 +65,35 @@ class DetailsHandler:
entry = self.app.hosts_file.entries[self.app.selected_entry_index]
# Add columns for labeled rows (Field, Value) - only if not already present
if not details_table.columns:
details_table.add_column("Field", key="field")
details_table.add_column("Value", key="value")
# Add rows in the same order as edit form
details_table.add_row("IP Address", entry.ip_address, key="ip")
details_table.add_row("Hostnames", ", ".join(entry.hostnames), key="hostnames")
details_table.add_row("Comment", entry.comment or "", key="comment")
details_table.add_row("Active", "Yes" if entry.is_active else "No", key="active")
# Add DNS name if present (not in edit form but good to show)
if entry.dns_name:
details_table.add_row("DNS Name", entry.dns_name, key="dns")
details_lines = [
f"IP Address: {entry.ip_address}",
f"Hostnames: {', '.join(entry.hostnames)}",
f"Status: {'Active' if entry.is_active else 'Inactive'}",
]
# Add notice for default system entries
if entry.is_default_entry():
details_table.add_row("", "", key="spacer")
details_table.add_row("⚠️ WARNING", "SYSTEM DEFAULT ENTRY", key="warning")
details_table.add_row("Note", "This entry cannot be modified", key="note")
details_lines.append("")
details_lines.append("⚠️ SYSTEM DEFAULT ENTRY")
details_lines.append(
"This is a default system entry and cannot be modified."
)
if entry.comment:
details_lines.append(f"Comment: {entry.comment}")
if entry.dns_name:
details_lines.append(f"DNS Name: {entry.dns_name}")
details_widget.update("\n".join(details_lines))
def update_edit_form(self) -> None:
"""Update the edit form with current entry values."""
details_table = self.app.query_one("#entry-details-table", DataTable)
details_widget = self.app.query_one("#entry-details", Static)
edit_form = self.app.query_one("#entry-edit-form")
# Hide details table, show edit form
details_table.add_class("hidden")
# Hide details, show edit form
details_widget.add_class("hidden")
edit_form.remove_class("hidden")
if not self.app.hosts_file.entries or self.app.selected_entry_index >= len(

View file

@ -57,21 +57,6 @@ HOSTS_MANAGER_CSS = """
display: none;
}
.status-bar {
height: 1;
width: 100%;
background: $error;
color: $text;
content-align: center middle;
layer: overlay;
dock: top;
offset-y: 1;
}
.status-bar.hidden {
display: none;
}
#entry-edit-form {
height: auto;
padding: 1;
@ -90,21 +75,4 @@ HOSTS_MANAGER_CSS = """
#entry-edit-form Checkbox {
margin-bottom: 1;
}
/* Entry details table styling */
#entry-details-table {
background: $background;
height: auto;
}
#entry-details-table .datatable--even-row {
background: $background;
}
#entry-details-table .datatable--odd-row {
background: $surface;
}
Header { height: 1; }
Header.-tall { height: 1; } /* Fix tall header also to height 1 */
"""

View file

@ -22,8 +22,8 @@ class TestHostsManagerApp:
with patch('hosts.tui.app.HostsParser'), patch('hosts.tui.app.Config'):
app = HostsManagerApp()
assert app.title == "/etc/hosts Manager"
assert app.sub_title == "" # Now set by update_status
assert app.title == "Hosts Manager"
assert app.sub_title == "Read-only mode"
assert app.edit_mode is False
assert app.selected_entry_index == 0
assert app.sort_column == ""
@ -74,7 +74,7 @@ class TestHostsManagerApp:
"""Test handling of missing hosts file."""
mock_parser = Mock(spec=HostsParser)
mock_config = Mock(spec=Config)
mock_parser.parse.side_effect = FileNotFoundError("Hosts file not found")
mock_parser.parse.side_effect = FileNotFoundError("File not found")
with patch('hosts.tui.app.HostsParser', return_value=mock_parser), \
patch('hosts.tui.app.Config', return_value=mock_config):
@ -85,7 +85,7 @@ class TestHostsManagerApp:
app.load_hosts_file()
# Should handle error gracefully
app.update_status.assert_called_with("Error loading hosts file: Hosts file not found")
app.update_status.assert_called_with("Error: Hosts file not found")
def test_load_hosts_file_permission_error(self):
"""Test handling of permission denied error."""
@ -102,7 +102,7 @@ class TestHostsManagerApp:
app.load_hosts_file()
# Should handle error gracefully
app.update_status.assert_called_with("Error loading hosts file: Permission denied")
app.update_status.assert_called_with("Error: Permission denied")
def test_populate_entries_table_logic(self):
"""Test populating DataTable logic without UI dependencies."""
@ -142,26 +142,15 @@ 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 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
# Mock the query_one method
mock_details = Mock()
app.query_one = Mock(return_value=mock_details)
# Add test entry
app.hosts_file = HostsFile()
@ -175,12 +164,12 @@ class TestHostsManagerApp:
app.update_entry_details()
# 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()
# 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
def test_update_entry_details_no_entries(self):
"""Test updating entry details with no entries."""
@ -192,29 +181,16 @@ class TestHostsManagerApp:
app = HostsManagerApp()
# 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()
# Mock the query_one method
mock_details = Mock()
app.query_one = Mock(return_value=mock_details)
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 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")
# Verify update was called with "No entries loaded"
mock_details.update.assert_called_once_with("No entries loaded")
def test_update_status_default(self):
"""Test status bar update with default information."""
@ -231,6 +207,10 @@ class TestHostsManagerApp:
app = HostsManagerApp()
# Mock the query_one method
mock_status = Mock()
app.query_one = Mock(return_value=mock_status)
# Add test entries
app.hosts_file = HostsFile()
app.hosts_file.add_entry(HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]))
@ -242,10 +222,12 @@ class TestHostsManagerApp:
app.update_status()
# Verify sub_title was set correctly
assert "Read-only mode" in app.sub_title
assert "2 entries" in app.sub_title
assert "1 active" in app.sub_title
# Verify status was updated
mock_status.update.assert_called_once()
call_args = mock_status.update.call_args[0][0]
assert "Read-only mode" in call_args
assert "2 entries" in call_args
assert "1 active" in call_args
def test_update_status_custom_message(self):
"""Test status bar update with custom message."""
@ -257,24 +239,15 @@ class TestHostsManagerApp:
app = HostsManagerApp()
# 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))
# Mock the query_one method and set_timer to avoid event loop issues
mock_status = Mock()
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")
# 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 status was updated with custom message
mock_status.update.assert_called_once_with("Custom status message")
# Verify timer was set for auto-clearing
app.set_timer.assert_called_once()
@ -347,21 +320,19 @@ class TestHostsManagerApp:
app.hosts_file.add_entry(HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]))
app.hosts_file.add_entry(HostEntry(ip_address="10.0.0.1", hostnames=["test"]))
# Mock the table_handler methods to avoid UI queries
app.table_handler.populate_entries_table = Mock()
app.table_handler.restore_cursor_position = Mock()
app.populate_entries_table = Mock()
app.update_status = Mock()
app.action_sort_by_ip()
# Check that entries are sorted by IP address
assert app.hosts_file.entries[0].ip_address == "10.0.0.1" # Sorted by IP
assert app.hosts_file.entries[1].ip_address == "127.0.0.1"
# Check that entries are sorted with default entries on top
assert app.hosts_file.entries[0].ip_address == "127.0.0.1" # Default entry first
assert app.hosts_file.entries[1].ip_address == "10.0.0.1" # Then sorted non-defaults
assert app.hosts_file.entries[2].ip_address == "192.168.1.1"
assert app.sort_column == "ip"
assert app.sort_ascending is True
app.table_handler.populate_entries_table.assert_called_once()
app.populate_entries_table.assert_called_once()
def test_action_sort_by_hostname_ascending(self):
"""Test sorting by hostname in ascending order."""
@ -379,9 +350,7 @@ class TestHostsManagerApp:
app.hosts_file.add_entry(HostEntry(ip_address="192.168.1.1", hostnames=["alpha"]))
app.hosts_file.add_entry(HostEntry(ip_address="10.0.0.1", hostnames=["beta"]))
# Mock the table_handler methods to avoid UI queries
app.table_handler.populate_entries_table = Mock()
app.table_handler.restore_cursor_position = Mock()
app.populate_entries_table = Mock()
app.update_status = Mock()
app.action_sort_by_hostname()
@ -393,7 +362,7 @@ class TestHostsManagerApp:
assert app.sort_column == "hostname"
assert app.sort_ascending is True
app.table_handler.populate_entries_table.assert_called_once()
app.populate_entries_table.assert_called_once()
def test_data_table_row_highlighted_event(self):
"""Test DataTable row highlighting event handling."""
@ -404,10 +373,10 @@ class TestHostsManagerApp:
patch('hosts.tui.app.Config', return_value=mock_config):
app = HostsManagerApp()
app.update_entry_details = Mock()
# Mock the details_handler and table_handler methods
app.details_handler.update_entry_details = Mock()
app.table_handler.display_index_to_actual_index = Mock(return_value=2)
# Mock the display_index_to_actual_index method to return the same index
app.display_index_to_actual_index = Mock(return_value=2)
# Create mock event with required parameters
mock_table = Mock()
@ -420,8 +389,8 @@ class TestHostsManagerApp:
# Should update selected index and details
assert app.selected_entry_index == 2
app.details_handler.update_entry_details.assert_called_once()
app.table_handler.display_index_to_actual_index.assert_called_once_with(2)
app.update_entry_details.assert_called_once()
app.display_index_to_actual_index.assert_called_once_with(2)
def test_data_table_header_selected_ip_column(self):
"""Test DataTable header selection for IP column."""

View file

@ -279,9 +279,7 @@ class TestSaveConfirmationIntegration:
"""Test exit_edit_entry_mode cleans up properly."""
app.entry_edit_mode = True
app.original_entry_values = {"test": "data"}
# Mock the details_handler and query_one methods
app.details_handler.update_entry_details = Mock()
app.update_entry_details = Mock()
app.query_one = Mock()
app.update_status = Mock()
@ -292,6 +290,6 @@ class TestSaveConfirmationIntegration:
assert not app.entry_edit_mode
assert app.original_entry_values is None
app.details_handler.update_entry_details.assert_called_once()
app.update_entry_details.assert_called_once()
mock_table.focus.assert_called_once()
app.update_status.assert_called_once_with("Exited entry edit mode")