diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 64cd232..a1d4e77 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -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**: diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 4bf3d73..9ffb0c7 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -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 diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index bfb8be1..047e999 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -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 diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index fbc95a5..e4fa75f 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -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 diff --git a/src/hosts/tui/app.py b/src/hosts/tui/app.py index 33660cd..4309555 100644 --- a/src/hosts/tui/app.py +++ b/src/hosts/tui/app.py @@ -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: diff --git a/src/hosts/tui/details_handler.py b/src/hosts/tui/details_handler.py index 762ce81..09d5514 100644 --- a/src/hosts/tui/details_handler.py +++ b/src/hosts/tui/details_handler.py @@ -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( diff --git a/src/hosts/tui/styles.py b/src/hosts/tui/styles.py index cfee74a..3b2551e 100644 --- a/src/hosts/tui/styles.py +++ b/src/hosts/tui/styles.py @@ -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 */ """ diff --git a/tests/test_main.py b/tests/test_main.py index 03c36fd..2851847 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -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.""" diff --git a/tests/test_save_confirmation_modal.py b/tests/test_save_confirmation_modal.py index 705309c..3f87b0b 100644 --- a/tests/test_save_confirmation_modal.py +++ b/tests/test_save_confirmation_modal.py @@ -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")