Enhance save confirmation modal: adjust button focus behavior and update tests for new functionality

This commit is contained in:
Philip Henning 2025-07-30 14:38:39 +02:00
parent f7671db43e
commit 77d8e647f2
2 changed files with 83 additions and 2 deletions

View file

@ -25,7 +25,7 @@ class SaveConfirmationModal(ModalScreen):
.save-confirmation-container { .save-confirmation-container {
width: 60; width: 60;
height: 12; height: 15;
background: $surface; background: $surface;
border: thick $primary; border: thick $primary;
padding: 1; padding: 1;
@ -52,6 +52,10 @@ class SaveConfirmationModal(ModalScreen):
margin: 0 1; margin: 0 1;
min-width: 12; min-width: 12;
} }
.save-confirmation-button:focus {
border: thick $accent;
}
""" """
BINDINGS = [ BINDINGS = [
@ -90,6 +94,60 @@ class SaveConfirmationModal(ModalScreen):
classes="save-confirmation-button", classes="save-confirmation-button",
) )
def on_mount(self) -> None:
"""Called when the modal is mounted. Set focus and ensure modal captures input."""
# Set focus to the modal screen itself first
self.focus()
# Then focus on the Save button
self.call_after_refresh(self._focus_save_button)
def _focus_save_button(self) -> None:
"""Focus the save button after refresh."""
save_button = self.query_one("#save-button", Button)
save_button.focus()
def on_key(self, event) -> None:
"""Handle key events, ensuring tab navigation works within the modal."""
if event.key == "tab":
# Get all buttons in order
buttons = [
self.query_one("#save-button", Button),
self.query_one("#discard-button", Button),
self.query_one("#cancel-button", Button),
]
# Find currently focused button and move to next
for i, button in enumerate(buttons):
if button.has_focus:
next_button = buttons[(i + 1) % len(buttons)]
next_button.focus()
event.prevent_default()
return
# If no button has focus, focus the first one
buttons[0].focus()
event.prevent_default()
elif event.key == "shift+tab":
# Get all buttons in order
buttons = [
self.query_one("#save-button", Button),
self.query_one("#discard-button", Button),
self.query_one("#cancel-button", Button),
]
# Find currently focused button and move to previous
for i, button in enumerate(buttons):
if button.has_focus:
prev_button = buttons[(i - 1) % len(buttons)]
prev_button.focus()
event.prevent_default()
return
# If no button has focus, focus the last one
buttons[-1].focus()
event.prevent_default()
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button presses.""" """Handle button presses."""
if event.button.id == "save-button": if event.button.id == "save-button":

View file

@ -6,7 +6,7 @@ This module tests the save confirmation functionality when exiting edit entry mo
import pytest import pytest
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from textual.widgets import Input, Checkbox from textual.widgets import Input, Checkbox, Button
from hosts.main import HostsManagerApp from hosts.main import HostsManagerApp
from hosts.core.models import HostsFile, HostEntry from hosts.core.models import HostsFile, HostEntry
@ -56,6 +56,29 @@ class TestSaveConfirmationModal:
modal.dismiss.assert_called_once_with("cancel") modal.dismiss.assert_called_once_with("cancel")
@patch.object(SaveConfirmationModal, "call_after_refresh")
@patch.object(SaveConfirmationModal, "focus")
def test_on_mount_sets_focus(self, mock_focus, mock_call_after_refresh):
"""Test that on_mount sets focus to the modal and schedules button focus."""
modal = SaveConfirmationModal()
modal.on_mount()
mock_focus.assert_called_once()
mock_call_after_refresh.assert_called_once()
@patch.object(SaveConfirmationModal, "query_one")
def test_focus_save_button(self, mock_query_one):
"""Test that _focus_save_button focuses the save button."""
modal = SaveConfirmationModal()
mock_save_button = Mock()
mock_query_one.return_value = mock_save_button
modal._focus_save_button()
mock_query_one.assert_called_once_with("#save-button", Button)
mock_save_button.focus.assert_called_once()
class TestSaveConfirmationIntegration: class TestSaveConfirmationIntegration:
"""Test cases for save confirmation integration with the main app.""" """Test cases for save confirmation integration with the main app."""