gardesk/garcard / efcb112

Browse files

Test keyboard-first prompt lifecycle behavior

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
efcb1120b8c4416b37d47ed938d76c09dcb28988
Parents
17012a9
Tree
92e0a23

1 changed file

StatusFile+-
M garcard/src/prompt_ui.rs 214 63
garcard/src/prompt_ui.rsmodified
@@ -561,69 +561,21 @@ impl PromptDialog {
561561
     }
562562
 
563563
     fn handle_key(&mut self, key_event: &KeyEvent) {
564
-        if self.request.feedback_only {
565
-            // Feedback dialogs are transient; ignore keypresses so the submit key
566
-            // from the previous prompt cannot dismiss success/error feedback early.
567
-            return;
568
-        }
569
-
570
-        match key_event.key {
571
-            Key::Escape => {
572
-                self.exit = Some(PromptExit::Canceled);
573
-            }
574
-            Key::Return => {
575
-                let submitted = std::mem::take(&mut self.input);
576
-                self.cursor = 0;
577
-                self.exit = Some(PromptExit::Submitted(submitted));
578
-            }
579
-            Key::Left => {
580
-                if self.cursor > 0 {
581
-                    self.cursor -= 1;
582
-                }
583
-            }
584
-            Key::Right => {
585
-                if self.cursor < self.input.chars().count() {
586
-                    self.cursor += 1;
587
-                }
588
-            }
589
-            Key::Home => {
590
-                self.cursor = 0;
591
-            }
592
-            Key::End => {
593
-                self.cursor = self.input.chars().count();
594
-            }
595
-            Key::Backspace => {
596
-                remove_char_before(&mut self.input, &mut self.cursor);
597
-            }
598
-            Key::Delete => {
599
-                remove_char_at(&mut self.input, self.cursor);
600
-            }
601
-            Key::Space => {
602
-                if key_event.modifiers.is_empty() || key_event.modifiers.shift {
603
-                    insert_char_at(&mut self.input, self.cursor, ' ');
604
-                    self.cursor += 1;
605
-                }
606
-            }
607
-            Key::Char(ch) => {
608
-                if key_event.modifiers.ctrl
609
-                    || key_event.modifiers.alt
610
-                    || key_event.modifiers.super_key
611
-                {
612
-                    return;
613
-                }
614
-                let resolved = self
615
-                    .keymap
616
-                    .as_ref()
617
-                    .and_then(|keymap| keymap.char_for_event(key_event))
618
-                    .unwrap_or(ch);
619
-                if resolved.is_control() {
620
-                    return;
621
-                }
622
-                insert_char_at(&mut self.input, self.cursor, resolved);
623
-                self.cursor += 1;
624
-            }
625
-            _ => {}
626
-        }
564
+        let resolved_char = if matches!(key_event.key, Key::Char(_)) {
565
+            self.keymap
566
+                .as_ref()
567
+                .and_then(|keymap| keymap.char_for_event(key_event))
568
+        } else {
569
+            None
570
+        };
571
+        apply_key_event(
572
+            &mut self.input,
573
+            &mut self.cursor,
574
+            &mut self.exit,
575
+            self.request.feedback_only,
576
+            key_event,
577
+            resolved_char,
578
+        );
627579
     }
628580
 
629581
     fn render(&mut self) -> Result<()> {
@@ -816,6 +768,73 @@ fn display_value(input: &str, mode: PromptMode) -> String {
816768
     }
817769
 }
818770
 
771
+fn apply_key_event(
772
+    input: &mut String,
773
+    cursor: &mut usize,
774
+    exit: &mut Option<PromptExit>,
775
+    feedback_only: bool,
776
+    key_event: &KeyEvent,
777
+    resolved_char: Option<char>,
778
+) {
779
+    if feedback_only {
780
+        // Feedback dialogs are transient; ignore keypresses so submit/escape
781
+        // from the previous prompt cannot dismiss success/error feedback early.
782
+        return;
783
+    }
784
+
785
+    match key_event.key {
786
+        Key::Escape => {
787
+            *exit = Some(PromptExit::Canceled);
788
+        }
789
+        Key::Return => {
790
+            let submitted = std::mem::take(input);
791
+            *cursor = 0;
792
+            *exit = Some(PromptExit::Submitted(submitted));
793
+        }
794
+        Key::Left => {
795
+            if *cursor > 0 {
796
+                *cursor -= 1;
797
+            }
798
+        }
799
+        Key::Right => {
800
+            if *cursor < input.chars().count() {
801
+                *cursor += 1;
802
+            }
803
+        }
804
+        Key::Home => {
805
+            *cursor = 0;
806
+        }
807
+        Key::End => {
808
+            *cursor = input.chars().count();
809
+        }
810
+        Key::Backspace => {
811
+            remove_char_before(input, cursor);
812
+        }
813
+        Key::Delete => {
814
+            remove_char_at(input, *cursor);
815
+        }
816
+        Key::Space => {
817
+            if key_event.modifiers.is_empty() || key_event.modifiers.shift {
818
+                insert_char_at(input, *cursor, ' ');
819
+                *cursor += 1;
820
+            }
821
+        }
822
+        Key::Char(ch) => {
823
+            if key_event.modifiers.ctrl || key_event.modifiers.alt || key_event.modifiers.super_key
824
+            {
825
+                return;
826
+            }
827
+            let resolved = resolved_char.unwrap_or(ch);
828
+            if resolved.is_control() {
829
+                return;
830
+            }
831
+            insert_char_at(input, *cursor, resolved);
832
+            *cursor += 1;
833
+        }
834
+        _ => {}
835
+    }
836
+}
837
+
819838
 fn display_prefix(input: &str, cursor: usize, mode: PromptMode) -> String {
820839
     let prefix = prefix_chars(input, cursor);
821840
     match mode {
@@ -873,6 +892,16 @@ fn scrub_string(value: &mut String) {
873892
 #[cfg(test)]
874893
 mod tests {
875894
     use super::*;
895
+    use gartk_core::Modifiers;
896
+
897
+    fn key_event(key: Key) -> KeyEvent {
898
+        KeyEvent {
899
+            key,
900
+            keycode: 0,
901
+            modifiers: Modifiers::NONE,
902
+            pressed: true,
903
+        }
904
+    }
876905
 
877906
     #[test]
878907
     fn display_value_masks_secret_text() {
@@ -949,4 +978,126 @@ mod tests {
949978
         assert_eq!(spanish.label_password, "Contrasena");
950979
         assert_eq!(spanish.footer_wait, "Espere");
951980
     }
981
+
982
+    #[test]
983
+    fn apply_key_event_submits_and_clears_input_on_return() {
984
+        let mut input = "secret".to_string();
985
+        let mut cursor = input.chars().count();
986
+        let mut exit = None;
987
+
988
+        apply_key_event(
989
+            &mut input,
990
+            &mut cursor,
991
+            &mut exit,
992
+            false,
993
+            &key_event(Key::Return),
994
+            None,
995
+        );
996
+
997
+        assert_eq!(cursor, 0);
998
+        assert!(input.is_empty());
999
+        assert_eq!(exit, Some(PromptExit::Submitted("secret".to_string())));
1000
+    }
1001
+
1002
+    #[test]
1003
+    fn apply_key_event_ignores_keys_for_feedback_only_dialogs() {
1004
+        let mut input = "ok".to_string();
1005
+        let mut cursor = 1;
1006
+        let mut exit = None;
1007
+
1008
+        apply_key_event(
1009
+            &mut input,
1010
+            &mut cursor,
1011
+            &mut exit,
1012
+            true,
1013
+            &key_event(Key::Escape),
1014
+            None,
1015
+        );
1016
+
1017
+        assert_eq!(input, "ok");
1018
+        assert_eq!(cursor, 1);
1019
+        assert!(exit.is_none());
1020
+    }
1021
+
1022
+    #[test]
1023
+    fn apply_key_event_respects_navigation_and_edit_shortcuts() {
1024
+        let mut input = "abcd".to_string();
1025
+        let mut cursor = 2;
1026
+        let mut exit = None;
1027
+
1028
+        apply_key_event(
1029
+            &mut input,
1030
+            &mut cursor,
1031
+            &mut exit,
1032
+            false,
1033
+            &key_event(Key::Left),
1034
+            None,
1035
+        );
1036
+        assert_eq!(cursor, 1);
1037
+
1038
+        apply_key_event(
1039
+            &mut input,
1040
+            &mut cursor,
1041
+            &mut exit,
1042
+            false,
1043
+            &key_event(Key::Backspace),
1044
+            None,
1045
+        );
1046
+        assert_eq!(input, "bcd");
1047
+        assert_eq!(cursor, 0);
1048
+
1049
+        apply_key_event(
1050
+            &mut input,
1051
+            &mut cursor,
1052
+            &mut exit,
1053
+            false,
1054
+            &key_event(Key::End),
1055
+            None,
1056
+        );
1057
+        assert_eq!(cursor, 3);
1058
+
1059
+        apply_key_event(
1060
+            &mut input,
1061
+            &mut cursor,
1062
+            &mut exit,
1063
+            false,
1064
+            &key_event(Key::Delete),
1065
+            None,
1066
+        );
1067
+        assert_eq!(input, "bcd");
1068
+
1069
+        apply_key_event(
1070
+            &mut input,
1071
+            &mut cursor,
1072
+            &mut exit,
1073
+            false,
1074
+            &key_event(Key::Home),
1075
+            None,
1076
+        );
1077
+        assert_eq!(cursor, 0);
1078
+    }
1079
+
1080
+    #[test]
1081
+    fn apply_key_event_ignores_ctrl_shortcuts_and_control_chars() {
1082
+        let mut input = String::new();
1083
+        let mut cursor = 0;
1084
+        let mut exit = None;
1085
+
1086
+        let mut ctrl_event = key_event(Key::Char('x'));
1087
+        ctrl_event.modifiers.ctrl = true;
1088
+        apply_key_event(&mut input, &mut cursor, &mut exit, false, &ctrl_event, None);
1089
+        assert!(input.is_empty());
1090
+        assert_eq!(cursor, 0);
1091
+
1092
+        apply_key_event(
1093
+            &mut input,
1094
+            &mut cursor,
1095
+            &mut exit,
1096
+            false,
1097
+            &key_event(Key::Char('a')),
1098
+            Some('\n'),
1099
+        );
1100
+        assert!(input.is_empty());
1101
+        assert_eq!(cursor, 0);
1102
+    }
9521103
 }