diff --git a/src/duration.rs b/src/duration.rs index c5357df..566350d 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1,6 +1,7 @@ use std::fmt; use std::time::Duration; +pub const ONE_DECI_SECOND: Duration = Duration::from_millis(100); pub const ONE_SECOND: Duration = Duration::from_secs(1); pub const ONE_MINUTE: Duration = Duration::from_secs(SECS_PER_MINUTE); pub const ONE_HOUR: Duration = Duration::from_secs(MINS_PER_HOUR * SECS_PER_MINUTE); @@ -140,4 +141,20 @@ mod tests { let ex: DurationEx = Duration::from_secs(1).into(); assert_eq!(format!("{}", ex), "1"); } + + #[test] + fn test_saturating_sub() { + let ex: DurationEx = Duration::from_secs(10).into(); + let ex2: DurationEx = Duration::from_secs(1).into(); + let ex3 = ex.saturating_sub(ex2); + assert_eq!(format!("{}", ex3), "9"); + } + + #[test] + fn test_saturating_add() { + let ex: DurationEx = Duration::from_secs(10).into(); + let ex2: DurationEx = Duration::from_secs(1).into(); + let ex3 = ex.saturating_add(ex2); + assert_eq!(format!("{}", ex3), "11"); + } } diff --git a/src/widgets.rs b/src/widgets.rs index 065b3d0..b3d47a4 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,4 +1,6 @@ pub mod clock; +#[cfg(test)] +pub mod clock_test; pub mod countdown; pub mod footer; pub mod header; diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 71b8e6b..877e5a0 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -13,7 +13,10 @@ use ratatui::{ }; use crate::{ - duration::{DurationEx, MINS_PER_HOUR, ONE_HOUR, ONE_MINUTE, ONE_SECOND, SECS_PER_MINUTE}, + duration::{ + DurationEx, MINS_PER_HOUR, ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND, + SECS_PER_MINUTE, + }, utils::center_horizontal, }; @@ -23,6 +26,7 @@ const MAX_DURATION: Duration = #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)] pub enum Time { + Decis, Seconds, Minutes, Hours, @@ -47,6 +51,7 @@ impl fmt::Display for Mode { Mode::Tick => write!(f, ">"), Mode::Pause => write!(f, "||"), Mode::Editable(time, _) => match time { + Time::Decis => write!(f, "[edit deciseconds]"), Time::Seconds => write!(f, "[edit seconds]"), Time::Minutes => write!(f, "[edit minutes]"), Time::Hours => write!(f, "[edit hours]"), @@ -168,7 +173,18 @@ impl Clock { } pub fn edit_current_up(&mut self) { - match self.mode { + self.current_value = match self.mode { + Mode::Editable(Time::Decis, _) => { + if self + .current_value + // < 99:59:58 + .le(&MAX_DURATION.saturating_sub(ONE_DECI_SECOND).into()) + { + self.current_value.saturating_add(ONE_DECI_SECOND.into()) + } else { + self.current_value + } + } Mode::Editable(Time::Seconds, _) => { if self .current_value @@ -208,6 +224,9 @@ impl Clock { } pub fn edit_current_down(&mut self) { self.current_value = match self.mode { + Mode::Editable(Time::Decis, _) => { + self.current_value.saturating_sub(ONE_DECI_SECOND.into()) + } Mode::Editable(Time::Seconds, _) => { self.current_value.saturating_sub(ONE_SECOND.into()) } @@ -236,14 +255,25 @@ impl Clock { fn edit_mode_next(&mut self) { let mode = self.mode.clone(); self.mode = match mode { - Mode::Editable(Time::Seconds, prev) if self.format >= Format::MSs => { - Mode::Editable(Time::Minutes, prev) + Mode::Editable(Time::Decis, prev) => Mode::Editable(Time::Seconds, prev), + Mode::Editable(Time::Seconds, prev) if self.format <= Format::Ss && self.with_decis => { + Mode::Editable(Time::Decis, prev) + } + Mode::Editable(Time::Seconds, prev) if self.format <= Format::Ss => { + Mode::Editable(Time::Seconds, prev) + } + Mode::Editable(Time::Seconds, prev) => Mode::Editable(Time::Minutes, prev), + Mode::Editable(Time::Minutes, prev) + if self.format <= Format::MmSs && self.with_decis => + { + Mode::Editable(Time::Decis, prev) } Mode::Editable(Time::Minutes, prev) if self.format <= Format::MmSs => { Mode::Editable(Time::Seconds, prev) } - Mode::Editable(Time::Minutes, prev) if self.format >= Format::MmSs => { - Mode::Editable(Time::Hours, prev) + Mode::Editable(Time::Minutes, prev) => Mode::Editable(Time::Hours, prev), + Mode::Editable(Time::Hours, prev) if self.with_decis => { + Mode::Editable(Time::Decis, prev) } Mode::Editable(Time::Hours, prev) => Mode::Editable(Time::Seconds, prev), _ => mode, @@ -254,12 +284,27 @@ impl Clock { fn edit_mode_prev(&mut self) { let mode = self.mode.clone(); self.mode = match mode { - Mode::Editable(Time::Seconds, prev) if self.format >= Format::HMmSs => { + Mode::Editable(Time::Decis, prev) if self.format <= Format::Ss => { + Mode::Editable(Time::Seconds, prev) + } + Mode::Editable(Time::Decis, prev) if self.format <= Format::MmSs => { + Mode::Editable(Time::Minutes, prev) + } + Mode::Editable(Time::Decis, prev) if self.format <= Format::HhMmSs => { Mode::Editable(Time::Hours, prev) } + Mode::Editable(Time::Seconds, prev) if self.with_decis => { + Mode::Editable(Time::Decis, prev) + } + Mode::Editable(Time::Seconds, prev) if self.format <= Format::Ss => { + Mode::Editable(Time::Seconds, prev) + } Mode::Editable(Time::Seconds, prev) if self.format <= Format::MmSs => { Mode::Editable(Time::Minutes, prev) } + Mode::Editable(Time::Seconds, prev) if self.format <= Format::HhMmSs => { + Mode::Editable(Time::Hours, prev) + } Mode::Editable(Time::Minutes, prev) => Mode::Editable(Time::Seconds, prev), Mode::Editable(Time::Hours, prev) => Mode::Editable(Time::Minutes, prev), _ => mode, @@ -374,18 +419,14 @@ impl Clock { pub fn edit_up(&mut self) { self.edit_current_up(); - // update `initial_value` if needed + // re-align `current_value` if needed if self.initial_value.lt(&self.current_value) { - self.initial_value = self.current_value; + self.current_value = self.initial_value; } } pub fn edit_down(&mut self) { self.edit_current_down(); - // update `initial_value` if needed - if self.initial_value.gt(&self.current_value) { - self.initial_value = self.current_value; - } } } @@ -797,6 +838,7 @@ where let edit_hours = matches!(state.mode, Mode::Editable(Time::Hours, _)); let edit_minutes = matches!(state.mode, Mode::Editable(Time::Minutes, _)); let edit_secs = matches!(state.mode, Mode::Editable(Time::Seconds, _)); + let edit_deci = matches!(state.mode, Mode::Editable(Time::Decis, _)); match format { Format::HhMmSs if with_decis => { let [hh, _, h, c_hm, mm, _, m, c_ms, ss, _, s, d, ds] = @@ -840,7 +882,7 @@ where buf, ); self.render_dot(symbol, d, buf); - self.render_digit(state.current_value.decis(), symbol, false, ds, buf); + self.render_digit(state.current_value.decis(), symbol, edit_deci, ds, buf); } Format::HhMmSs => { let [hh, _, h, c_hm, mm, _, m, c_ms, ss, _, s] = @@ -919,7 +961,7 @@ where buf, ); self.render_dot(symbol, d, buf); - self.render_digit(state.current_value.decis(), symbol, false, ds, buf); + self.render_digit(state.current_value.decis(), symbol, edit_deci, ds, buf); } Format::HMmSs => { let [h, c_hm, mm, _, m, c_ms, ss, _, s] = @@ -989,7 +1031,7 @@ where buf, ); self.render_dot(symbol, d, buf); - self.render_digit(state.current_value.decis(), symbol, false, ds, buf); + self.render_digit(state.current_value.decis(), symbol, edit_deci, ds, buf); } Format::MmSs => { let [mm, _, m, c_ms, ss, _, s] = @@ -1050,7 +1092,7 @@ where buf, ); self.render_dot(symbol, d, buf); - self.render_digit(state.current_value.decis(), symbol, false, ds, buf); + self.render_digit(state.current_value.decis(), symbol, edit_deci, ds, buf); } Format::MSs => { let [m, c_ms, ss, _, s] = @@ -1096,7 +1138,7 @@ where buf, ); self.render_dot(symbol, d, buf); - self.render_digit(state.current_value.decis(), symbol, false, ds, buf); + self.render_digit(state.current_value.decis(), symbol, edit_deci, ds, buf); } Format::Ss => { let [ss, _, s] = Layout::horizontal(Constraint::from_lengths(widths)).areas(area); @@ -1125,7 +1167,7 @@ where buf, ); self.render_dot(symbol, d, buf); - self.render_digit(state.current_value.decis(), symbol, false, ds, buf); + self.render_digit(state.current_value.decis(), symbol, edit_deci, ds, buf); } Format::S => { let [s] = Layout::horizontal(Constraint::from_lengths(widths)).areas(area); diff --git a/src/widgets/clock_test.rs b/src/widgets/clock_test.rs new file mode 100644 index 0000000..1afeba8 --- /dev/null +++ b/src/widgets/clock_test.rs @@ -0,0 +1,418 @@ +use crate::{ + duration::{ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND}, + widgets::clock::*, +}; +use std::time::Duration; + +#[test] +fn test_toggle_edit() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_HOUR, + current_value: ONE_HOUR, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + // off by default + assert!(!c.is_edit_mode()); + // toggle on + c.toggle_edit(); + assert!(c.is_edit_mode()); + // toggle off + c.toggle_edit(); + assert!(!c.is_edit_mode()); +} + +#[test] +fn test_default_edit_mode_hhmmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_HOUR, + current_value: ONE_HOUR, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_default_edit_mode_mmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_MINUTE, + current_value: ONE_MINUTE, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_default_edit_mode_ss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_SECOND, + current_value: ONE_SECOND, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); +} + +#[test] +fn test_edit_next_hhmmssd() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_HOUR, + current_value: ONE_HOUR, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Hours, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Decis, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_edit_next_hhmmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_HOUR, + current_value: ONE_HOUR, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Hours, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_edit_next_mmssd() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_MINUTE, + current_value: ONE_MINUTE, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Decis, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_edit_next_mmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_MINUTE, + current_value: ONE_MINUTE, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_edit_next_ssd() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_SECOND * 3, + current_value: ONE_SECOND * 3, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + c.edit_next(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Decis, _))); +} + +#[test] +fn test_edit_next_ss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_SECOND * 3, + current_value: ONE_SECOND * 3, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + c.edit_next(); + println!("mode -> {:?}", c.get_mode()); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); +} + +#[test] +fn test_edit_prev_hhmmssd() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_HOUR, + current_value: ONE_HOUR, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Decis, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Hours, _))); +} + +#[test] +fn test_edit_prev_hhmmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_HOUR, + current_value: ONE_HOUR, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Hours, _))); +} + +#[test] +fn test_edit_prev_mmssd() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_MINUTE, + current_value: ONE_MINUTE, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Decis, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_edit_prev_mmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_MINUTE, + current_value: ONE_MINUTE, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Minutes, _))); +} + +#[test] +fn test_edit_prev_ssd() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_SECOND, + current_value: ONE_SECOND, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: true, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Decis, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); +} + +#[test] +fn test_edit_prev_ss() { + let mut c = Clock::::new(ClockArgs { + initial_value: ONE_SECOND, + current_value: ONE_SECOND, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); + c.edit_prev(); + assert!(matches!(c.get_mode(), Mode::Editable(Time::Seconds, _))); +} + +#[test] +fn test_edit_up_ss() { + let mut c = Clock::::new(ClockArgs { + initial_value: Duration::ZERO, + current_value: Duration::ZERO, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + // +1s + c.edit_up(); + assert_eq!(Duration::from(*c.get_current_value()), ONE_SECOND); +} + +#[test] +fn test_edit_up_mmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: Duration::ZERO, + current_value: Duration::from_secs(60), + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + // +1m + c.edit_up(); + assert_eq!( + Duration::from(*c.get_current_value()), + Duration::from_secs(120) + ); +} + +#[test] +fn test_edit_up_hhmmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: Duration::ZERO, + current_value: Duration::from_secs(3600), + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + // edit hh + c.edit_next(); + // +1h + c.edit_up(); + assert_eq!( + Duration::from(*c.get_current_value()), + Duration::from_secs(3600 + 3600) + ); +} + +#[test] +fn test_edit_down_ss() { + let mut c = Clock::::new(ClockArgs { + initial_value: Duration::ZERO, + current_value: ONE_SECOND, + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + // -1s + c.edit_down(); + assert_eq!(Duration::from(*c.get_current_value()), Duration::ZERO); + // and again: -1s + c.edit_down(); + // still ZERO + assert_eq!(Duration::from(*c.get_current_value()), Duration::ZERO); +} + +#[test] +fn test_edit_down_mmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: Duration::ZERO, + current_value: Duration::from_secs(120), + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + // -1m + c.edit_down(); + assert_eq!( + Duration::from(*c.get_current_value()), + Duration::from_secs(60) + ); + // and again: -1m + c.edit_down(); + assert_eq!(Duration::from(*c.get_current_value()), Duration::ZERO); +} + +#[test] +fn test_edit_down_hhmmss() { + let mut c = Clock::::new(ClockArgs { + initial_value: Duration::ZERO, + current_value: Duration::from_secs(3600), + tick_value: ONE_DECI_SECOND, + style: Style::default(), + with_decis: false, + }); + + // toggle on + c.toggle_edit(); + // edit hh + c.edit_next(); + // +1h + c.edit_down(); + assert_eq!(Duration::from(*c.get_current_value()), Duration::ZERO); +}