Skip to main content

kimun_notes/components/settings/
vault_section.rs

1use ratatui::Frame;
2use ratatui::layout::Rect;
3use ratatui::widgets::{Block, Borders, Paragraph};
4use std::path::PathBuf;
5
6use crate::components::Component;
7use crate::components::event_state::EventState;
8use crate::components::events::{AppEvent, AppTx, InputEvent};
9use crate::settings::themes::Theme;
10
11pub struct VaultSection {
12    current_path: Option<PathBuf>,
13}
14
15impl VaultSection {
16    pub fn new(current_path: Option<PathBuf>) -> Self {
17        Self { current_path }
18    }
19
20    pub fn set_path(&mut self, path: Option<PathBuf>) {
21        self.current_path = path;
22    }
23}
24
25impl Component for VaultSection {
26    fn handle_input(&mut self, event: &InputEvent, tx: &AppTx) -> EventState {
27        let InputEvent::Key(key) = event else {
28            return EventState::NotConsumed;
29        };
30        match key.code {
31            ratatui::crossterm::event::KeyCode::Enter
32            | ratatui::crossterm::event::KeyCode::Char('b') => {
33                tx.send(AppEvent::OpenFileBrowser).ok();
34                EventState::Consumed
35            }
36            _ => EventState::NotConsumed,
37        }
38    }
39
40    fn render(&mut self, f: &mut Frame, rect: Rect, theme: &Theme, focused: bool) {
41        let border_style = theme.border_style(focused);
42        let path_str = self
43            .current_path
44            .as_ref()
45            .map(|p| p.to_string_lossy().into_owned())
46            .unwrap_or_else(|| "(no vault set)".to_string());
47        let text = format!("{}    [Enter: Browse]", path_str);
48        let block = Block::default()
49            .title("Vault Path")
50            .borders(Borders::ALL)
51            .border_style(border_style)
52            .style(theme.base_style());
53        let para = Paragraph::new(text).block(block).style(theme.base_style());
54        f.render_widget(para, rect);
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn renders_no_vault_set_when_none() {
64        let section = VaultSection::new(None);
65        assert!(section.current_path.is_none());
66    }
67
68    #[test]
69    fn renders_path_when_some() {
70        let path = PathBuf::from("/Users/me/notes");
71        let section = VaultSection::new(Some(path.clone()));
72        assert_eq!(section.current_path.as_ref().unwrap(), &path);
73    }
74
75    #[test]
76    fn set_path_updates_current() {
77        let mut section = VaultSection::new(None);
78        let path = PathBuf::from("/Users/me/notes");
79        section.set_path(Some(path.clone()));
80        assert_eq!(section.current_path.as_ref().unwrap(), &path);
81    }
82
83    #[test]
84    fn enter_sends_open_file_browser() {
85        use crate::components::events::AppEvent;
86        use ratatui::crossterm::event::{
87            KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
88        };
89        let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
90        let mut section = VaultSection::new(None);
91        let key = InputEvent::Key(KeyEvent {
92            code: KeyCode::Enter,
93            modifiers: KeyModifiers::NONE,
94            kind: KeyEventKind::Press,
95            state: KeyEventState::NONE,
96        });
97        let result = section.handle_input(&key, &tx);
98        assert!(matches!(
99            result,
100            crate::components::event_state::EventState::Consumed
101        ));
102        let msg = rx.try_recv().expect("message should be sent");
103        assert!(matches!(msg, AppEvent::OpenFileBrowser));
104    }
105
106    #[test]
107    fn b_key_sends_open_file_browser() {
108        use crate::components::events::AppEvent;
109        use ratatui::crossterm::event::{
110            KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
111        };
112        let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
113        let mut section = VaultSection::new(None);
114        let key = InputEvent::Key(KeyEvent {
115            code: KeyCode::Char('b'),
116            modifiers: KeyModifiers::NONE,
117            kind: KeyEventKind::Press,
118            state: KeyEventState::NONE,
119        });
120        let result = section.handle_input(&key, &tx);
121        assert!(matches!(
122            result,
123            crate::components::event_state::EventState::Consumed
124        ));
125        let msg = rx.try_recv().expect("message should be sent");
126        assert!(matches!(msg, AppEvent::OpenFileBrowser));
127    }
128
129    #[test]
130    fn renders_no_vault_set_text() {
131        use ratatui::Terminal;
132        use ratatui::backend::TestBackend;
133        let backend = TestBackend::new(60, 5);
134        let mut terminal = Terminal::new(backend).unwrap();
135        let mut section = VaultSection::new(None);
136        let theme = crate::settings::themes::Theme::gruvbox_dark();
137        terminal
138            .draw(|f| {
139                section.render(f, f.area(), &theme, false);
140            })
141            .unwrap();
142        let buffer = terminal.backend().buffer().clone();
143        let flat: String = buffer.content.iter().map(|c| c.symbol()).collect();
144        assert!(
145            flat.contains("(no vault set)"),
146            "Expected '(no vault set)' in rendered output"
147        );
148    }
149
150    #[test]
151    fn renders_vault_path_text() {
152        use ratatui::Terminal;
153        use ratatui::backend::TestBackend;
154        let backend = TestBackend::new(60, 5);
155        let mut terminal = Terminal::new(backend).unwrap();
156        let path = PathBuf::from("/Users/me/notes");
157        let mut section = VaultSection::new(Some(path));
158        let theme = crate::settings::themes::Theme::gruvbox_dark();
159        terminal
160            .draw(|f| {
161                section.render(f, f.area(), &theme, false);
162            })
163            .unwrap();
164        let buffer = terminal.backend().buffer().clone();
165        let flat: String = buffer.content.iter().map(|c| c.symbol()).collect();
166        assert!(
167            flat.contains("notes"),
168            "Expected vault path in rendered output"
169        );
170    }
171}