- Implemented SaveConfirmationModal to prompt users for saving changes when exiting edit mode. - Integrated modal into HostsManagerApp to handle unsaved changes. - Added methods to validate and save entry changes, restoring original values if discarded. - Created unit tests for SaveConfirmationModal and its integration with the main application. - Refactored entry editing logic to track changes and confirm before exiting edit mode.
284 lines
9.7 KiB
Python
284 lines
9.7 KiB
Python
"""
|
|
Tests for the save confirmation modal.
|
|
|
|
This module tests the save confirmation functionality when exiting edit entry mode.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
from textual.widgets import Input, Checkbox
|
|
|
|
from hosts.main import HostsManagerApp
|
|
from hosts.core.models import HostsFile, HostEntry
|
|
from hosts.tui.save_confirmation_modal import SaveConfirmationModal
|
|
|
|
|
|
class TestSaveConfirmationModal:
|
|
"""Test cases for the SaveConfirmationModal class."""
|
|
|
|
def test_modal_creation(self):
|
|
"""Test that the modal can be created."""
|
|
modal = SaveConfirmationModal()
|
|
assert modal is not None
|
|
|
|
def test_modal_compose(self):
|
|
"""Test that the modal composes correctly."""
|
|
# Note: Cannot test compose() directly without app context
|
|
# This is a basic existence check for the modal
|
|
modal = SaveConfirmationModal()
|
|
assert hasattr(modal, "compose")
|
|
assert callable(modal.compose)
|
|
|
|
def test_action_save(self):
|
|
"""Test save action dismisses with 'save'."""
|
|
modal = SaveConfirmationModal()
|
|
modal.dismiss = Mock()
|
|
|
|
modal.action_save()
|
|
|
|
modal.dismiss.assert_called_once_with("save")
|
|
|
|
def test_action_discard(self):
|
|
"""Test discard action dismisses with 'discard'."""
|
|
modal = SaveConfirmationModal()
|
|
modal.dismiss = Mock()
|
|
|
|
modal.action_discard()
|
|
|
|
modal.dismiss.assert_called_once_with("discard")
|
|
|
|
def test_action_cancel(self):
|
|
"""Test cancel action dismisses with 'cancel'."""
|
|
modal = SaveConfirmationModal()
|
|
modal.dismiss = Mock()
|
|
|
|
modal.action_cancel()
|
|
|
|
modal.dismiss.assert_called_once_with("cancel")
|
|
|
|
|
|
class TestSaveConfirmationIntegration:
|
|
"""Test cases for save confirmation integration with the main app."""
|
|
|
|
@pytest.fixture
|
|
def app(self):
|
|
"""Create a test app instance."""
|
|
return HostsManagerApp()
|
|
|
|
def test_has_entry_changes_no_original_values(self, app):
|
|
"""Test has_entry_changes returns False when no original values stored."""
|
|
app.original_entry_values = None
|
|
app.entry_edit_mode = True
|
|
|
|
assert not app.has_entry_changes()
|
|
|
|
def test_has_entry_changes_not_in_edit_mode(self, app):
|
|
"""Test has_entry_changes returns False when not in edit mode."""
|
|
app.original_entry_values = {
|
|
"ip_address": "127.0.0.1",
|
|
"hostnames": ["localhost"],
|
|
"comment": None,
|
|
"is_active": True,
|
|
}
|
|
app.entry_edit_mode = False
|
|
|
|
assert not app.has_entry_changes()
|
|
|
|
@patch.object(HostsManagerApp, "query_one")
|
|
def test_has_entry_changes_no_changes(self, mock_query_one, app):
|
|
"""Test has_entry_changes returns False when no changes made."""
|
|
# Setup original values
|
|
app.original_entry_values = {
|
|
"ip_address": "127.0.0.1",
|
|
"hostnames": ["localhost"],
|
|
"comment": None,
|
|
"is_active": True,
|
|
}
|
|
app.entry_edit_mode = True
|
|
|
|
# Mock form fields with original values
|
|
mock_ip_input = Mock()
|
|
mock_ip_input.value = "127.0.0.1"
|
|
mock_hostname_input = Mock()
|
|
mock_hostname_input.value = "localhost"
|
|
mock_comment_input = Mock()
|
|
mock_comment_input.value = ""
|
|
mock_checkbox = Mock()
|
|
mock_checkbox.value = True
|
|
|
|
def mock_query_side_effect(selector, widget_type=None):
|
|
if selector == "#ip-input":
|
|
return mock_ip_input
|
|
elif selector == "#hostname-input":
|
|
return mock_hostname_input
|
|
elif selector == "#comment-input":
|
|
return mock_comment_input
|
|
elif selector == "#active-checkbox":
|
|
return mock_checkbox
|
|
|
|
mock_query_one.side_effect = mock_query_side_effect
|
|
|
|
assert not app.has_entry_changes()
|
|
|
|
@patch.object(HostsManagerApp, "query_one")
|
|
def test_has_entry_changes_ip_changed(self, mock_query_one, app):
|
|
"""Test has_entry_changes returns True when IP address changed."""
|
|
# Setup original values
|
|
app.original_entry_values = {
|
|
"ip_address": "127.0.0.1",
|
|
"hostnames": ["localhost"],
|
|
"comment": None,
|
|
"is_active": True,
|
|
}
|
|
app.entry_edit_mode = True
|
|
|
|
# Mock form fields with changed IP
|
|
mock_ip_input = Mock()
|
|
mock_ip_input.value = "192.168.1.1" # Changed IP
|
|
mock_hostname_input = Mock()
|
|
mock_hostname_input.value = "localhost"
|
|
mock_comment_input = Mock()
|
|
mock_comment_input.value = ""
|
|
mock_checkbox = Mock()
|
|
mock_checkbox.value = True
|
|
|
|
def mock_query_side_effect(selector, widget_type=None):
|
|
if selector == "#ip-input":
|
|
return mock_ip_input
|
|
elif selector == "#hostname-input":
|
|
return mock_hostname_input
|
|
elif selector == "#comment-input":
|
|
return mock_comment_input
|
|
elif selector == "#active-checkbox":
|
|
return mock_checkbox
|
|
|
|
mock_query_one.side_effect = mock_query_side_effect
|
|
|
|
assert app.has_entry_changes()
|
|
|
|
@patch.object(HostsManagerApp, "query_one")
|
|
def test_has_entry_changes_hostname_changed(self, mock_query_one, app):
|
|
"""Test has_entry_changes returns True when hostname changed."""
|
|
# Setup original values
|
|
app.original_entry_values = {
|
|
"ip_address": "127.0.0.1",
|
|
"hostnames": ["localhost"],
|
|
"comment": None,
|
|
"is_active": True,
|
|
}
|
|
app.entry_edit_mode = True
|
|
|
|
# Mock form fields with changed hostname
|
|
mock_ip_input = Mock()
|
|
mock_ip_input.value = "127.0.0.1"
|
|
mock_hostname_input = Mock()
|
|
mock_hostname_input.value = "localhost, test.local" # Added hostname
|
|
mock_comment_input = Mock()
|
|
mock_comment_input.value = ""
|
|
mock_checkbox = Mock()
|
|
mock_checkbox.value = True
|
|
|
|
def mock_query_side_effect(selector, widget_type=None):
|
|
if selector == "#ip-input":
|
|
return mock_ip_input
|
|
elif selector == "#hostname-input":
|
|
return mock_hostname_input
|
|
elif selector == "#comment-input":
|
|
return mock_comment_input
|
|
elif selector == "#active-checkbox":
|
|
return mock_checkbox
|
|
|
|
mock_query_one.side_effect = mock_query_side_effect
|
|
|
|
assert app.has_entry_changes()
|
|
|
|
@patch.object(HostsManagerApp, "query_one")
|
|
def test_has_entry_changes_comment_added(self, mock_query_one, app):
|
|
"""Test has_entry_changes returns True when comment added."""
|
|
# Setup original values
|
|
app.original_entry_values = {
|
|
"ip_address": "127.0.0.1",
|
|
"hostnames": ["localhost"],
|
|
"comment": None,
|
|
"is_active": True,
|
|
}
|
|
app.entry_edit_mode = True
|
|
|
|
# Mock form fields with added comment
|
|
mock_ip_input = Mock()
|
|
mock_ip_input.value = "127.0.0.1"
|
|
mock_hostname_input = Mock()
|
|
mock_hostname_input.value = "localhost"
|
|
mock_comment_input = Mock()
|
|
mock_comment_input.value = "Test comment" # Added comment
|
|
mock_checkbox = Mock()
|
|
mock_checkbox.value = True
|
|
|
|
def mock_query_side_effect(selector, widget_type=None):
|
|
if selector == "#ip-input":
|
|
return mock_ip_input
|
|
elif selector == "#hostname-input":
|
|
return mock_hostname_input
|
|
elif selector == "#comment-input":
|
|
return mock_comment_input
|
|
elif selector == "#active-checkbox":
|
|
return mock_checkbox
|
|
|
|
mock_query_one.side_effect = mock_query_side_effect
|
|
|
|
assert app.has_entry_changes()
|
|
|
|
@patch.object(HostsManagerApp, "query_one")
|
|
def test_has_entry_changes_active_state_changed(self, mock_query_one, app):
|
|
"""Test has_entry_changes returns True when active state changed."""
|
|
# Setup original values
|
|
app.original_entry_values = {
|
|
"ip_address": "127.0.0.1",
|
|
"hostnames": ["localhost"],
|
|
"comment": None,
|
|
"is_active": True,
|
|
}
|
|
app.entry_edit_mode = True
|
|
|
|
# Mock form fields with changed active state
|
|
mock_ip_input = Mock()
|
|
mock_ip_input.value = "127.0.0.1"
|
|
mock_hostname_input = Mock()
|
|
mock_hostname_input.value = "localhost"
|
|
mock_comment_input = Mock()
|
|
mock_comment_input.value = ""
|
|
mock_checkbox = Mock()
|
|
mock_checkbox.value = False # Changed active state
|
|
|
|
def mock_query_side_effect(selector, widget_type=None):
|
|
if selector == "#ip-input":
|
|
return mock_ip_input
|
|
elif selector == "#hostname-input":
|
|
return mock_hostname_input
|
|
elif selector == "#comment-input":
|
|
return mock_comment_input
|
|
elif selector == "#active-checkbox":
|
|
return mock_checkbox
|
|
|
|
mock_query_one.side_effect = mock_query_side_effect
|
|
|
|
assert app.has_entry_changes()
|
|
|
|
def test_exit_edit_entry_mode(self, app):
|
|
"""Test exit_edit_entry_mode cleans up properly."""
|
|
app.entry_edit_mode = True
|
|
app.original_entry_values = {"test": "data"}
|
|
app.update_entry_details = Mock()
|
|
app.query_one = Mock()
|
|
app.update_status = Mock()
|
|
|
|
mock_table = Mock()
|
|
app.query_one.return_value = mock_table
|
|
|
|
app.exit_edit_entry_mode()
|
|
|
|
assert not app.entry_edit_mode
|
|
assert app.original_entry_values is None
|
|
app.update_entry_details.assert_called_once()
|
|
mock_table.focus.assert_called_once()
|
|
app.update_status.assert_called_once_with("Exited entry edit mode")
|