Skip to main content

fresh/view/
popup_input.rs

1//! Input handling for Popups.
2//!
3//! Implements the InputHandler trait for PopupManager.
4//! Delegates to popup-specific handlers based on PopupKind.
5
6use super::popup::input::handle_popup_input;
7use super::popup::PopupManager;
8use crate::input::handler::{InputContext, InputHandler, InputResult};
9use crossterm::event::KeyEvent;
10
11impl InputHandler for PopupManager {
12    fn handle_key_event(&mut self, event: &KeyEvent, ctx: &mut InputContext) -> InputResult {
13        // Only handle if there are active popups
14        if !self.is_visible() {
15            return InputResult::Ignored;
16        }
17
18        // Get the topmost popup and delegate to the appropriate handler
19        if let Some(popup) = self.top_mut() {
20            handle_popup_input(event, popup, ctx)
21        } else {
22            InputResult::Ignored
23        }
24    }
25
26    fn is_modal(&self) -> bool {
27        self.is_visible()
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::input::handler::DeferredAction;
35    use crate::view::popup::{Popup, PopupKind, PopupListItem};
36    use crate::view::theme;
37    use crate::view::theme::Theme;
38    use crossterm::event::{KeyCode, KeyModifiers};
39
40    fn key(code: KeyCode) -> KeyEvent {
41        KeyEvent::new(code, KeyModifiers::NONE)
42    }
43
44    fn create_popup_with_items(count: usize) -> PopupManager {
45        let theme = Theme::load_builtin(theme::THEME_DARK).unwrap();
46        let items: Vec<PopupListItem> = (0..count)
47            .map(|i| PopupListItem::new(format!("Item {}", i)))
48            .collect();
49        let popup = Popup::list(items, &theme).with_kind(PopupKind::Action);
50        let mut manager = PopupManager::new();
51        manager.show(popup);
52        manager
53    }
54
55    #[test]
56    fn test_popup_navigation() {
57        let mut manager = create_popup_with_items(5);
58        let mut ctx = InputContext::new();
59
60        // Initially at item 0
61        assert_eq!(
62            manager.top().unwrap().selected_item().unwrap().text,
63            "Item 0"
64        );
65
66        // Down arrow moves to next
67        manager.handle_key_event(&key(KeyCode::Down), &mut ctx);
68        assert_eq!(
69            manager.top().unwrap().selected_item().unwrap().text,
70            "Item 1"
71        );
72
73        // Up arrow moves back
74        manager.handle_key_event(&key(KeyCode::Up), &mut ctx);
75        assert_eq!(
76            manager.top().unwrap().selected_item().unwrap().text,
77            "Item 0"
78        );
79    }
80
81    #[test]
82    fn test_popup_enter_confirms() {
83        let mut manager = create_popup_with_items(3);
84        let mut ctx = InputContext::new();
85
86        manager.handle_key_event(&key(KeyCode::Enter), &mut ctx);
87        assert!(ctx
88            .deferred_actions
89            .iter()
90            .any(|a| matches!(a, DeferredAction::ConfirmPopup)));
91    }
92
93    #[test]
94    fn test_popup_escape_cancels() {
95        let mut manager = create_popup_with_items(3);
96        let mut ctx = InputContext::new();
97
98        manager.handle_key_event(&key(KeyCode::Esc), &mut ctx);
99        assert!(ctx
100            .deferred_actions
101            .iter()
102            .any(|a| matches!(a, DeferredAction::ClosePopup)));
103    }
104
105    #[test]
106    fn test_popup_is_modal_when_visible() {
107        let mut manager = PopupManager::new();
108        assert!(!manager.is_modal());
109
110        let theme = Theme::load_builtin(theme::THEME_DARK).unwrap();
111        manager.show(Popup::text(vec!["test".to_string()], &theme));
112        assert!(manager.is_modal());
113    }
114
115    #[test]
116    fn test_popup_ignored_when_empty() {
117        let mut manager = PopupManager::new();
118        let mut ctx = InputContext::new();
119
120        let result = manager.handle_key_event(&key(KeyCode::Down), &mut ctx);
121        assert_eq!(result, InputResult::Ignored);
122    }
123}