a lightweight, interval-based utility to combat digital strain through "Ma" (intentional pauses) for the eyes and body.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add tests for types and profile

+172
+45
src/config/types.rs
··· 139 139 } 140 140 } 141 141 142 + #[cfg(test)] 143 + mod tests { 144 + use super::OverlayTheme::{Dark, Light, System}; 145 + 146 + #[test] 147 + fn to_mode_index_concrete() { 148 + assert_eq!(System.to_mode_index(), 0); 149 + assert_eq!(Light.to_mode_index(), 1); 150 + assert_eq!(Dark.to_mode_index(), 2); 151 + } 152 + 153 + #[test] 154 + fn from_mode_index_concrete() { 155 + use super::OverlayTheme; 156 + assert_eq!(OverlayTheme::from_mode_index(0), System); 157 + assert_eq!(OverlayTheme::from_mode_index(1), Light); 158 + assert_eq!(OverlayTheme::from_mode_index(2), Dark); 159 + } 160 + 161 + #[test] 162 + fn from_mode_index_unknown_returns_system() { 163 + use super::OverlayTheme; 164 + assert_eq!(OverlayTheme::from_mode_index(99), System); 165 + assert_eq!(OverlayTheme::from_mode_index(-1), System); 166 + } 167 + 168 + #[test] 169 + fn round_trip_system() { 170 + use super::OverlayTheme; 171 + assert_eq!(OverlayTheme::from_mode_index(System.to_mode_index()), System); 172 + } 173 + 174 + #[test] 175 + fn round_trip_light() { 176 + use super::OverlayTheme; 177 + assert_eq!(OverlayTheme::from_mode_index(Light.to_mode_index()), Light); 178 + } 179 + 180 + #[test] 181 + fn round_trip_dark() { 182 + use super::OverlayTheme; 183 + assert_eq!(OverlayTheme::from_mode_index(Dark.to_mode_index()), Dark); 184 + } 185 + } 186 + 142 187 impl ProfileConfig { 143 188 pub fn pomodoro() -> Self { 144 189 Self {
+127
src/timer/profile.rs
··· 92 92 .expect("profile must have at least one level") 93 93 } 94 94 } 95 + 96 + #[cfg(test)] 97 + mod tests { 98 + use super::*; 99 + use crate::config::{AppearanceConfig, AppSettings, EnforcedConfig}; 100 + use std::collections::HashMap; 101 + 102 + fn profile_cfg(name: &str, mode: BreakModeConfig) -> ProfileConfig { 103 + ProfileConfig { 104 + name: name.to_string(), 105 + mode, 106 + snooze_secs: 300, 107 + idle_threshold_secs: 300, 108 + idle_detection_enabled: false, 109 + levels: vec![BreakLevelConfig { 110 + work_secs: 1500, 111 + break_secs: 300, 112 + label: "Short break".to_string(), 113 + }], 114 + long_break: None, 115 + } 116 + } 117 + 118 + fn make_config(active: &str, keys: &[&str]) -> AppConfig { 119 + let mut profiles = HashMap::new(); 120 + for &k in keys { 121 + profiles.insert(k.to_string(), profile_cfg(k, BreakModeConfig::Reminder)); 122 + } 123 + AppConfig { 124 + app: AppSettings { active_profile: active.to_string(), autostart: false }, 125 + profiles, 126 + appearance: AppearanceConfig::default(), 127 + enforced: EnforcedConfig::default(), 128 + } 129 + } 130 + 131 + #[test] 132 + fn from_config_single_level() { 133 + let cfg = profile_cfg("Test", BreakModeConfig::Reminder); 134 + let p = Profile::from_config(&cfg); 135 + assert_eq!(p.levels.len(), 1); 136 + assert_eq!(p.levels[0].work_duration, Duration::from_secs(1500)); 137 + assert_eq!(p.levels[0].break_duration, Duration::from_secs(300)); 138 + assert_eq!(p.mode, BreakMode::Reminder); 139 + assert_eq!(p.snooze_duration, Duration::from_secs(300)); 140 + assert!(p.long_break.is_none()); 141 + } 142 + 143 + #[test] 144 + fn from_config_enforced_mode() { 145 + let cfg = profile_cfg("Test", BreakModeConfig::Enforced); 146 + let p = Profile::from_config(&cfg); 147 + assert_eq!(p.mode, BreakMode::Enforced); 148 + } 149 + 150 + #[test] 151 + fn from_config_levels_sorted_ascending() { 152 + let cfg = ProfileConfig { 153 + name: "Test".to_string(), 154 + mode: BreakModeConfig::Reminder, 155 + snooze_secs: 300, 156 + idle_threshold_secs: 300, 157 + idle_detection_enabled: false, 158 + levels: vec![ 159 + BreakLevelConfig { work_secs: 3600, break_secs: 600, label: "Long".to_string() }, 160 + BreakLevelConfig { work_secs: 600, break_secs: 60, label: "Short".to_string() }, 161 + ], 162 + long_break: None, 163 + }; 164 + let p = Profile::from_config(&cfg); 165 + assert_eq!(p.levels.len(), 2); 166 + assert!(p.levels[0].work_duration < p.levels[1].work_duration); 167 + } 168 + 169 + #[test] 170 + fn from_config_long_break_mapped() { 171 + let cfg = ProfileConfig { 172 + name: "Test".to_string(), 173 + mode: BreakModeConfig::Reminder, 174 + snooze_secs: 300, 175 + idle_threshold_secs: 300, 176 + idle_detection_enabled: false, 177 + levels: vec![BreakLevelConfig { 178 + work_secs: 1500, 179 + break_secs: 300, 180 + label: "Break".to_string(), 181 + }], 182 + long_break: Some(LongBreakConfig { 183 + after_cycles: 4, 184 + max_cycle_gap_secs: 1800, 185 + break_secs: 900, 186 + label: "Long break".to_string(), 187 + }), 188 + }; 189 + let p = Profile::from_config(&cfg); 190 + let lb = p.long_break.as_ref().expect("long break should be set"); 191 + assert_eq!(lb.after_cycles, 4); 192 + assert_eq!(lb.break_duration, Duration::from_secs(900)); 193 + assert_eq!(lb.max_cycle_gap, Duration::from_secs(1800)); 194 + } 195 + 196 + #[test] 197 + fn active_profile_uses_active_key() { 198 + let cfg = make_config("b", &["a", "b"]); 199 + let p = active_profile(&cfg); 200 + assert_eq!(p.name, "b"); 201 + } 202 + 203 + #[test] 204 + fn active_profile_fallback_when_missing() { 205 + let cfg = make_config("nonexistent", &["only"]); 206 + let p = active_profile(&cfg); 207 + assert_eq!(p.name, "only"); 208 + } 209 + 210 + #[test] 211 + #[should_panic] 212 + fn active_profile_panics_on_empty() { 213 + let cfg = AppConfig { 214 + app: AppSettings { active_profile: "x".to_string(), autostart: false }, 215 + profiles: HashMap::new(), 216 + appearance: AppearanceConfig::default(), 217 + enforced: EnforcedConfig::default(), 218 + }; 219 + active_profile(&cfg); 220 + } 221 + }