Feat #2: add Host Entry from selected
This commit is contained in:
parent
6ce1d7da2f
commit
f2e2d45480
9 changed files with 213 additions and 11 deletions
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue