Show all dialogs and the guest table as titled boxes
The info screen's style now applies everywhere. The guest table sits in a "Guests" frame with the active context in its bottom border. Every dialog is a centred box with a title, labelled fields and the buttons bottom right: confirm (titled by the action), edit resources, switch context, back up and edit network. Fixed: - The network dialog hid most of its fields. In Horizontal rows Textual gave Select a width of 1 and Input 100 %, which pushed bridge, firewall, the IP mode selects and the gateways off-screen. Every field in a row now has an explicit width. - A guest name or note containing markup, such as "a[/b]", crashed the guest table and the dialogs, because DataTable cells and border titles were parsed as markup. They are now plain text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62490af913
commit
9ff00e425f
2 files changed
+246
-66
No files matched your search
@@ -1883,3 +1883,106 @@ async def test_info_has_separate_titled_backup_and_network_sections(fake):
|
||||
"unavailable",
|
||||
"addresses",
|
||||
}
|
||||
|
||||
|
||||
# --- boxed design: every dialog a titled box, main table in a titled frame ----------
|
||||
|
||||
from textual.content import Content
|
||||
from textual.widgets import Label
|
||||
|
||||
SIZE = (100, 40)
|
||||
|
||||
|
||||
def visible(app, widget):
|
||||
r = widget.region
|
||||
return (
|
||||
r.width >= 3
|
||||
and r.height >= 1
|
||||
and r.right <= app.size.width
|
||||
and r.bottom <= app.size.height
|
||||
)
|
||||
|
||||
|
||||
def label_texts(screen):
|
||||
return {str(w.render()).strip() for w in screen.query(Label)}
|
||||
|
||||
|
||||
async def test_net_screen_shows_every_field_inside_the_screen(fake):
|
||||
# bug: in the old rows Select got width 1 and Input 100 %, pushing fields off-screen
|
||||
fake.net_configs[100] = dict(CT_CONFIG)
|
||||
app = make_app()
|
||||
async with app.run_test(size=SIZE) as pilot:
|
||||
await open_net(app, pilot)
|
||||
for wid in ("iface", "bridge", "tag", "firewall", "ipv4", "ipv4_cidr", "ipv4_gw",
|
||||
"ipv6", "ipv6_cidr", "ipv6_gw", "apply", "cancel"): # fmt: skip
|
||||
assert visible(app, app.screen.query_one(f"#{wid}")), wid
|
||||
|
||||
|
||||
async def test_net_screen_labels_and_ip_sections(fake):
|
||||
fake.net_configs[100] = dict(CT_CONFIG)
|
||||
app = make_app()
|
||||
async with app.run_test(size=SIZE) as pilot:
|
||||
await open_net(app, pilot)
|
||||
assert {"Interface", "Bridge", "VLAN"} <= label_texts(app.screen)
|
||||
assert "IPv4" in str(app.screen.query_one("#ipv4_section").border_title)
|
||||
assert "IPv6" in str(app.screen.query_one("#ipv6_section").border_title)
|
||||
|
||||
|
||||
async def test_edit_screen_fields_have_labels(fake):
|
||||
app = make_app()
|
||||
async with app.run_test(size=SIZE) as pilot:
|
||||
await open_edit(app, pilot)
|
||||
assert {"Cores", "Memory (MiB)", "Disk", "Grow by (GiB)"} <= label_texts(
|
||||
app.screen
|
||||
)
|
||||
|
||||
|
||||
async def open_each_dialog(app, pilot):
|
||||
"""Yields after each dialog is open; closes it with escape afterwards."""
|
||||
await settle(app, pilot)
|
||||
for keys in (["S"], ["e"], ["c"], ["i", "b"], ["i", "n"]):
|
||||
for k in keys:
|
||||
await pilot.press(k)
|
||||
await settle(app, pilot)
|
||||
yield keys
|
||||
for _ in keys:
|
||||
await pilot.press("escape")
|
||||
await settle(app, pilot)
|
||||
|
||||
|
||||
async def test_every_dialog_is_a_titled_centred_box(fake):
|
||||
fake.net_configs[108] = dict(VM_CONFIG)
|
||||
app = make_app()
|
||||
async with app.run_test(size=SIZE) as pilot:
|
||||
async for keys in open_each_dialog(app, pilot):
|
||||
box = app.screen.query_one("#dialog")
|
||||
assert str(box.border_title).strip(), keys
|
||||
assert box.region.width < app.size.width, keys # a box, not full screen
|
||||
assert box.region.x > 0, keys # centred
|
||||
|
||||
|
||||
async def test_main_table_is_framed_with_title_and_context(fake):
|
||||
app = make_app()
|
||||
async with app.run_test(size=SIZE) as pilot:
|
||||
await settle(app, pilot)
|
||||
frame = app.query_one("#guests_section")
|
||||
assert "Guests" in str(frame.border_title)
|
||||
assert "mainframe" in str(frame.border_subtitle)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("keys", [["e"], ["i", "b"], ["i", "n"]])
|
||||
async def test_guest_name_with_markup_characters_does_not_break_dialogs(fake, keys):
|
||||
# border titles are parsed as markup; a name like "a[/b]" must be shown literally
|
||||
fake.guests = [dict(VM, name="a[/b]")]
|
||||
fake.net_configs[108] = dict(VM_CONFIG)
|
||||
app = make_app()
|
||||
async with app.run_test(size=SIZE) as pilot:
|
||||
await settle(app, pilot)
|
||||
for k in keys:
|
||||
await pilot.press(k)
|
||||
await settle(app, pilot)
|
||||
title = app.screen.query_one("#dialog").border_title
|
||||
assert (
|
||||
"a[/b]" in Content.from_markup(title).plain
|
||||
) # what is shown, not how it is stored
|
||||
assert app.is_running
|
||||
Reference in new issue
Block a user