Fix keyboard shortcuts for shifted punctuation and letters
- Preserve Shift+R semantics for DNS refresh - Resolve shifted punctuation by produced text across layouts - Expand keyboard binding coverage tests
This commit is contained in:
parent
9040cf72a8
commit
d4130c332a
4 changed files with 124 additions and 2 deletions
|
|
@ -160,6 +160,74 @@ def test_enhanced_caps_lock_n_does_not_become_shift_n():
|
|||
assert event.character == "N"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sequence",
|
||||
(
|
||||
"\x1b[47;2;63u", # US: Shift+/
|
||||
"\x1b[223;2;63u", # German: Shift+ß
|
||||
),
|
||||
)
|
||||
def test_enhanced_question_mark_uses_the_produced_text_key(sequence):
|
||||
"""Shifted punctuation binds by its text across keyboard layouts."""
|
||||
event = list(HostsXTermParser().feed(sequence))[0]
|
||||
|
||||
assert isinstance(event, Key)
|
||||
assert event.key == "question_mark"
|
||||
assert event.character == "?"
|
||||
|
||||
|
||||
def test_keyboard_protocol_events_match_all_character_and_modifier_bindings():
|
||||
"""Terminal key events resolve to every app-level shortcut action."""
|
||||
sequences_and_actions = {
|
||||
"\x1b[110u": "add_entry",
|
||||
"\x1b[110;2;78u": "new_from_selected",
|
||||
"N": "new_from_selected",
|
||||
"\x1b[100u": "delete_entry",
|
||||
"\x1b[101u": "edit_entry",
|
||||
"\x1b[32u": "toggle_entry",
|
||||
"\x1b[101;5u": "toggle_edit_mode",
|
||||
"\x1b[99u": "config",
|
||||
"\x1b[120;5u": "export_entries",
|
||||
"\x1b[111;5u": "import_entries",
|
||||
"\x1b[102;5u": "show_filters",
|
||||
"\x1b[47;2;63u": "help",
|
||||
"?": "help",
|
||||
"\x1b[113u": "quit",
|
||||
"\x1b[114;5u": "reload",
|
||||
"\x1b[105u": "sort_by_ip",
|
||||
"\x1b[104u": "sort_by_hostname",
|
||||
"\x1b[115;5u": "save_file",
|
||||
"\x1b[98u": "restore_backup",
|
||||
"\x1b[1;2A": "move_entry_up",
|
||||
"\x1b[1;2B": "move_entry_down",
|
||||
"\x1b[122;5u": "undo",
|
||||
"\x1b[121;5u": "redo",
|
||||
"\x1b[114;2;82u": "refresh_dns",
|
||||
"R": "refresh_dns",
|
||||
"\x1b[114u": "update_single_dns",
|
||||
"\x1b[27u": "exit_edit_entry",
|
||||
"\t": "next_field",
|
||||
"\x1b[Z": "prev_field",
|
||||
"\x1b[9;2;9u": "prev_field",
|
||||
"\x1b[99;5u": "quit",
|
||||
}
|
||||
actions_by_key = {
|
||||
key: binding.action if hasattr(binding, "action") else binding[1]
|
||||
for binding in HostsManagerApp.BINDINGS
|
||||
for key in str(binding.key if hasattr(binding, "key") else binding[0]).split(
|
||||
","
|
||||
)
|
||||
}
|
||||
|
||||
for sequence, expected_action in sequences_and_actions.items():
|
||||
event = next(
|
||||
event
|
||||
for event in HostsXTermParser().feed(sequence)
|
||||
if isinstance(event, Key)
|
||||
)
|
||||
assert actions_by_key[event.key] == expected_action
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enhanced_shift_n_opens_new_from_selected_but_caps_lock_n_does_not():
|
||||
"""The real enhanced-key events distinguish Shift from Caps Lock at the app."""
|
||||
|
|
@ -234,6 +302,43 @@ async def test_new_from_selected_is_available_at_the_minimum_viewport():
|
|||
assert not isinstance(app.screen, AddEntryModal)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sequence", ("\x1b[47;2;63u", "\x1b[223;2;63u"))
|
||||
@pytest.mark.asyncio
|
||||
async def test_enhanced_question_mark_shortcut_opens_and_closes_help_overlay(sequence):
|
||||
"""Kitty Shift+? controls the dedicated help overlay."""
|
||||
app = app_with_filterable_entries()
|
||||
|
||||
async with app.run_test(size=(120, 40)) as pilot:
|
||||
app.query_one("#entries-table").focus()
|
||||
question_mark = list(HostsXTermParser().feed(sequence))[0]
|
||||
app.post_message(question_mark)
|
||||
await pilot.pause()
|
||||
|
||||
assert isinstance(app.screen, HelpModal)
|
||||
|
||||
question_mark = list(HostsXTermParser().feed(sequence))[0]
|
||||
app.post_message(question_mark)
|
||||
await pilot.pause()
|
||||
|
||||
assert not isinstance(app.screen, HelpModal)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enhanced_shift_r_shortcut_refreshes_all_dns_entries():
|
||||
"""Kitty Shift+R invokes the documented refresh-all action."""
|
||||
app = app_with_filterable_entries()
|
||||
|
||||
async with app.run_test(size=(120, 40)) as pilot:
|
||||
app.query_one("#entries-table").focus()
|
||||
shift_r = list(HostsXTermParser().feed("\x1b[114;2;82u"))[0]
|
||||
app.post_message(shift_r)
|
||||
await pilot.pause()
|
||||
|
||||
assert "Cannot resolve DNS names" in str(
|
||||
app.query_one("#message-rail", Static).render()
|
||||
)
|
||||
|
||||
|
||||
@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."""
|
||||
|
|
|
|||
|
|
@ -704,7 +704,7 @@ class TestHostsManagerApp:
|
|||
for binding in app.BINDINGS:
|
||||
if hasattr(binding, "key"):
|
||||
# Binding object
|
||||
binding_keys.append(binding.key)
|
||||
binding_keys.extend(str(binding.key).split(","))
|
||||
elif isinstance(binding, tuple) and len(binding) >= 1:
|
||||
# Tuple format (key, action, description)
|
||||
binding_keys.append(binding[0])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue