Feat #2: add Host Entry from selected

This commit is contained in:
Philip Henning 2026-09-06 16:43:48 +02:00
parent 6ce1d7da2f
commit f2e2d45480
9 changed files with 213 additions and 11 deletions

View file

@ -8,7 +8,7 @@ DNS name entries, validation, and mutual exclusion logic.
import pytest
from typing import cast
from unittest.mock import Mock
from textual.widgets import Input, Checkbox, RadioSet, Static
from textual.widgets import Input, Checkbox, RadioSet, RadioButton, Static
from src.hosts.tui.add_entry_modal import AddEntryModal
from src.hosts.core.models import HostEntry
@ -267,6 +267,50 @@ class TestAddEntryModalRadioButtonLogic:
mock_hostname_input.focus.assert_called_once()
@pytest.mark.asyncio
async def test_prefilled_dns_modal_keeps_separate_address_drafts_when_switching_type():
"""A copied DNS Entry retains both address drafts while its type is changed."""
from src.hosts.tui.app import HostsManagerApp
source = HostEntry(
ip_address="192.0.2.45",
hostnames=["source.test", "alias.test"],
comment="Copied",
is_active=True,
dns_name="origin.example",
resolved_ip="192.0.2.45",
dns_resolution_status="resolved",
)
app = HostsManagerApp()
app.hosts_file.entries = [source]
app.load_hosts_file = Mock()
async with app.run_test() as pilot:
app.push_screen(AddEntryModal(source))
await pilot.pause()
modal = app.screen
assert modal.query_one("#dns-entry-radio", RadioButton).value
assert modal.query_one("#dns-name-input", Input).value == "origin.example"
assert modal.query_one("#ip-address-input", Input).value == ""
assert not modal.query_one("#active-checkbox", Checkbox).value
assert modal.query_one("#active-checkbox", Checkbox).disabled
await pilot.click("#ip-entry-radio")
await pilot.pause()
assert modal.query_one("#ip-address-input", Input).value == ""
assert modal.query_one("#active-checkbox", Checkbox).value
assert not modal.query_one("#active-checkbox", Checkbox).disabled
modal.query_one("#ip-address-input", Input).value = "198.51.100.8"
await pilot.click("#dns-entry-radio")
await pilot.pause()
assert modal.query_one("#dns-name-input", Input).value == "origin.example"
await pilot.click("#ip-entry-radio")
await pilot.pause()
assert modal.query_one("#ip-address-input", Input).value == "198.51.100.8"
class TestAddEntryModalSaveLogic:
"""Test cases for save logic in AddEntryModal."""

View file

@ -19,6 +19,7 @@ from src.hosts.tui.custom_footer import CustomFooter
from src.hosts.tui.backup_restore_modal import BackupRestoreModal
from src.hosts.tui.filter_modal import FilterModal
from src.hosts.tui.help_modal import HelpModal
from src.hosts.tui.add_entry_modal import AddEntryModal
from src.hosts.tui.privilege_prompt import (
render_sudo_authentication_notice,
sudo_authentication_screen,
@ -97,6 +98,78 @@ async def test_filter_shortcut_does_not_stack_filter_modals():
assert sum(isinstance(screen, FilterModal) for screen in app.screen_stack) == 1
@pytest.mark.asyncio
async def test_new_from_selected_prefills_the_highlighted_visible_entry():
"""Shift+N snapshots the highlighted visible Host Entry into the Add form."""
app = app_with_filterable_entries()
source = app.hosts_file.entries[1]
source.hostnames = ["inactive.test", "alias.test"]
source.comment = "Copied comment"
source.is_active = False
app.edit_mode = True
app.sort_column = "hostname"
async with app.run_test(size=(120, 40)) as pilot:
app.table_handler.populate_entries_table()
app.table_handler.move_cursor_to_entry_index(1)
await pilot.pause()
await pilot.press("shift+n")
await pilot.pause()
assert isinstance(app.screen, AddEntryModal)
assert app.screen.query_one("#hostnames-input", Input).value == (
"inactive.test, alias.test"
)
assert app.screen.query_one("#ip-address-input", Input).value == "192.0.2.2"
assert app.screen.query_one("#comment-input", Input).value == "Copied comment"
assert not app.screen.query_one("#active-checkbox").value
assert "Based on: inactive.test" in str(
app.screen.query_one("#entry-source", Static).render()
)
assert app.focused is app.screen.query_one("#hostnames-input", Input)
assert source.hostnames == ["inactive.test", "alias.test"]
assert source.comment == "Copied comment"
assert not source.is_active
@pytest.mark.asyncio
async def test_new_from_selected_requires_a_visible_host_entry():
"""Shift+N does not reuse a stale selection when filters hide every entry."""
app = app_with_filterable_entries()
app.edit_mode = True
app.current_filter_options.search_term = "not-a-match"
async with app.run_test(size=(120, 40)) as pilot:
app.table_handler.populate_entries_table()
await pilot.press("shift+n")
await pilot.pause()
assert not isinstance(app.screen, AddEntryModal)
assert "No Host Entry selected; use n to add a blank Host Entry." in str(
app.query_one("#message-rail", Static).render()
)
@pytest.mark.asyncio
async def test_new_from_selected_is_available_at_the_minimum_viewport():
"""Shift+N keeps the selected-entry add workflow reachable at 100×30."""
app = app_with_filterable_entries()
app.edit_mode = True
async with app.run_test(size=(100, 30)) as pilot:
app.table_handler.populate_entries_table()
await pilot.press("shift+n")
await pilot.pause()
assert isinstance(app.screen, AddEntryModal)
assert app.focused is app.screen.query_one("#hostnames-input", Input)
await pilot.press("escape")
await pilot.pause()
assert not isinstance(app.screen, AddEntryModal)
@pytest.mark.asyncio
async def test_help_overlay_closes_to_the_control_that_opened_it():
"""Help is an overlay and returns keyboard focus to its opener."""