//! Application state template for tool TUI integration //! //! Copy and customize this template for your tool's app state. //! Replace {{TOOL_NAME}} with your tool name (e.g., MyTool, TrackingManager, etc.) //! Replace {{ITEM_TYPE}} with your primary data type use serde::{Deserialize, Serialize}; use std::fmt; /// Screen types - customize based on your tool's screens /// /// # Example /// ```ignore /// pub enum ScreenType { /// List, // Show all items /// Detail, // Show item details /// Create, // Create new item /// Settings, // Tool settings /// } /// ``` #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ScreenType { /// List screen - show all items List, /// Detail screen - show item details Detail, /// Create screen - create new items Create, } impl fmt::Display for ScreenType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ScreenType::List => write!(f, "List"), ScreenType::Detail => write!(f, "Detail"), ScreenType::Create => write!(f, "Create"), } } } /// Application state - manages all mutable state /// /// # Design Pattern: R-STATE-SEPARATION /// All state mutations go through explicit methods. Rendering functions /// only read state (take `&AppState`, never `&mut AppState`). /// /// # Dirty Flag /// The `dirty` flag tracks whether state changed since last render. /// Only redraw when `dirty` is true, then set to false. #[derive(Clone, Debug)] pub struct AppState { /// Current screen (navigation state) pub current_screen: ScreenType, /// Primary items collection - customize type as needed pub items: Vec, /// Currently selected item index pub selected_index: usize, /// Whether state changed (needs redraw) pub dirty: bool, /// Whether application should quit pub should_quit: bool, // === Form fields for create/edit === /// Form input: item name pub form_name: String, /// Form input: item description pub form_description: String, } impl AppState { /// Create new application state pub fn new() -> Self { AppState { current_screen: ScreenType::List, items: Vec::new(), selected_index: 0, dirty: true, should_quit: false, form_name: String::new(), form_description: String::new(), } } // === Item Management === /// Add a new item to the collection pub fn add_item(&mut self, item: String) { self.items.push(item); self.dirty = true; } /// Get currently selected item pub fn selected_item(&self) -> Option<&String> { self.items.get(self.selected_index) } // === Navigation === /// Select next item in list pub fn select_next(&mut self) { if !self.items.is_empty() { self.selected_index = (self.selected_index + 1) % self.items.len(); self.dirty = true; } } /// Select previous item in list pub fn select_previous(&mut self) { if !self.items.is_empty() { self.selected_index = if self.selected_index == 0 { self.items.len() - 1 } else { self.selected_index - 1 }; self.dirty = true; } } /// Switch to a different screen pub fn set_screen(&mut self, screen: ScreenType) { if self.current_screen != screen { self.current_screen = screen; self.dirty = true; } } // === Form Management === /// Reset form fields to empty pub fn reset_form(&mut self) { self.form_name.clear(); self.form_description.clear(); self.dirty = true; } // === Lifecycle === /// Request application quit pub fn request_quit(&mut self) { self.should_quit = true; } /// Check if state has changed since last render pub fn is_dirty(&self) -> bool { self.dirty } /// Mark state as rendered (clear dirty flag) /// /// Call this after successfully rendering to the terminal pub fn reset_dirty(&mut self) { self.dirty = false; } /// Check if should quit pub fn should_quit(&self) -> bool { self.should_quit } } impl Default for AppState { fn default() -> Self { Self::new() } } // ============================================================================ // Tests - Minimum 15+ tests per module // ============================================================================ #[cfg(test)] mod tests { use super::*; // === Initialization Tests === #[test] fn test_app_state_new() { let state = AppState::new(); assert_eq!(state.current_screen, ScreenType::List); assert!(state.items.is_empty()); assert_eq!(state.selected_index, 0); assert!(state.dirty); assert!(!state.should_quit); assert!(state.form_name.is_empty()); } #[test] fn test_app_state_default() { let state = AppState::default(); assert_eq!(state.current_screen, ScreenType::List); } // === Item Management Tests === #[test] fn test_add_item() { let mut state = AppState::new(); state.add_item("Item 1".to_string()); assert_eq!(state.items.len(), 1); assert!(state.dirty); } #[test] fn test_selected_item() { let mut state = AppState::new(); state.add_item("Item 1".to_string()); assert_eq!(state.selected_item(), Some(&"Item 1".to_string())); } #[test] fn test_selected_item_none() { let state = AppState::new(); assert!(state.selected_item().is_none()); } // === Navigation Tests === #[test] fn test_select_next() { let mut state = AppState::new(); state.add_item("Item 1".to_string()); state.add_item("Item 2".to_string()); state.reset_dirty(); state.select_next(); assert_eq!(state.selected_index, 1); assert!(state.dirty); } #[test] fn test_select_next_wraps() { let mut state = AppState::new(); state.add_item("Item 1".to_string()); state.add_item("Item 2".to_string()); state.selected_index = 1; state.reset_dirty(); state.select_next(); assert_eq!(state.selected_index, 0); } #[test] fn test_select_previous() { let mut state = AppState::new(); state.add_item("Item 1".to_string()); state.add_item("Item 2".to_string()); state.selected_index = 1; state.reset_dirty(); state.select_previous(); assert_eq!(state.selected_index, 0); } #[test] fn test_select_previous_wraps() { let mut state = AppState::new(); state.add_item("Item 1".to_string()); state.add_item("Item 2".to_string()); state.reset_dirty(); state.select_previous(); assert_eq!(state.selected_index, 1); } // === Screen Navigation Tests === #[test] fn test_set_screen() { let mut state = AppState::new(); state.reset_dirty(); state.set_screen(ScreenType::Detail); assert_eq!(state.current_screen, ScreenType::Detail); assert!(state.dirty); } #[test] fn test_set_screen_same_not_dirty() { let mut state = AppState::new(); state.reset_dirty(); state.set_screen(ScreenType::List); assert!(!state.dirty); } // === Form Management Tests === #[test] fn test_reset_form() { let mut state = AppState::new(); state.form_name = "Test".to_string(); state.form_description = "Description".to_string(); state.reset_dirty(); state.reset_form(); assert!(state.form_name.is_empty()); assert!(state.form_description.is_empty()); assert!(state.dirty); } // === Lifecycle Tests === #[test] fn test_request_quit() { let mut state = AppState::new(); assert!(!state.should_quit()); state.request_quit(); assert!(state.should_quit()); } #[test] fn test_dirty_flag() { let mut state = AppState::new(); assert!(state.is_dirty()); state.reset_dirty(); assert!(!state.is_dirty()); } }