test(keyboard): shortcuts, pin the key-up action list on both sides

kShortcutActionsRunOnKeyUp and runs_on_release are two hand-kept lists of
the actions that run on key release. Add a shared fixture and check each
side against it, the way the key vocabulary and the default bindings are
already checked, so adding a focus-changing action on one side breaks a
test until the other side follows. The Dart test also checks that every
listed id is a configurable action.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ra4my61t8q1FN5n59wB16D
This commit is contained in:
rustdesk
2026-09-23 20:57:25 +08:00
co-authored by Claude Fable 5.1
parent 1fbf3f5f7f
commit 18092b403d
4 changed files with 58 additions and 6 deletions
@@ -23,7 +23,8 @@ const kShortcutActionSwitchTabPrev = 'switch_tab_prev';
/// that consumed the press also sees its repeats and its release. On Linux a
/// legacy-mode session matches in Dart while a map-mode session matches in
/// Rust, and neither knows the other's fired keys. Mirrors `runs_on_release`
/// in `src/keyboard/shortcuts.rs`.
/// in `src/keyboard/shortcuts.rs`; both are checked against
/// `flutter/test/fixtures/key_up_shortcut_actions.json`.
const kShortcutActionsRunOnKeyUp = {
kShortcutActionCloseTab,
kShortcutActionSwitchTabNext,
+5
View File
@@ -0,0 +1,5 @@
[
"close_tab",
"switch_tab_next",
"switch_tab_prev"
]
+19
View File
@@ -509,6 +509,25 @@ void main() {
expect(shortcutKeyNameForEvent(qwertzZ), 'y');
});
test('kShortcutActionsRunOnKeyUp matches the fixture and names real actions',
() {
// Rust has a mirror test against the same file
// (`key_up_actions_match_fixture` in src/keyboard/shortcuts.rs).
final fixture = (jsonDecode(
File('test/fixtures/key_up_shortcut_actions.json')
.readAsStringSync()) as List<dynamic>)
.cast<String>()
.toSet();
expect(kShortcutActionsRunOnKeyUp, equals(fixture),
reason: 'kShortcutActionsRunOnKeyUp drifted from the fixture — update '
'shortcut_constants.dart, the fixture, and Rust runs_on_release '
'together');
final actions = idSet(kKeyboardShortcutActionGroups);
for (final id in kShortcutActionsRunOnKeyUp) {
expect(actions, contains(id), reason: '"$id" is not a configurable action');
}
});
test('configurable shortcut list does not include known-removed action IDs',
() {
// These IDs were briefly defined without handlers (a "ghost action"
+32 -5
View File
@@ -343,12 +343,16 @@ lazy_static::lazy_static! {
/// that consumed the press also sees its repeats and its release. The next
/// session may route its keys through the other matcher (Flutter's legacy
/// path on Linux), which never saw the press. Mirrors
/// `kShortcutActionsRunOnKeyUp` in `shortcut_constants.dart`.
/// `kShortcutActionsRunOnKeyUp` in `shortcut_constants.dart`; both are
/// checked against `flutter/test/fixtures/key_up_shortcut_actions.json`.
const RELEASE_ACTION_IDS: &[&str] = &[
action_id::CLOSE_TAB,
action_id::SWITCH_TAB_NEXT,
action_id::SWITCH_TAB_PREV,
];
pub fn runs_on_release(action_id: &str) -> bool {
matches!(
action_id,
action_id::CLOSE_TAB | action_id::SWITCH_TAB_NEXT | action_id::SWITCH_TAB_PREV
)
RELEASE_ACTION_IDS.contains(&action_id)
}
/// Forget the modifiers a session released on its remote and the actions it
@@ -1237,6 +1241,29 @@ mod tests {
release_chord(chord);
}
/// The Dart matcher keeps the same list (`kShortcutActionsRunOnKeyUp`);
/// the fixture is the shared source of truth.
#[test]
fn key_up_actions_match_fixture() {
use std::collections::BTreeSet;
let fixture: Vec<String> = serde_json::from_str(include_str!(
"../../flutter/test/fixtures/key_up_shortcut_actions.json"
))
.expect("parse fixture");
let expected: BTreeSet<&str> = fixture.iter().map(String::as_str).collect();
let actual: BTreeSet<&str> = RELEASE_ACTION_IDS.iter().copied().collect();
assert_eq!(
actual, expected,
"runs_on_release drifted from key_up_shortcut_actions.json — update \
shortcuts.rs, the fixture, and Dart kShortcutActionsRunOnKeyUp together"
);
for id in &fixture {
assert!(runs_on_release(id));
}
assert!(!runs_on_release(action_id::SCREENSHOT));
}
/// Close tab and tab switching move focus away before the key is
/// released; they run on the release so the same matcher sees the whole
/// press.