2026-07-22 19:57:29 -05:00
|
|
|
use std::{
|
2026-07-27 15:00:07 -05:00
|
|
|
io::{self, stdout}, sync::{Arc, Mutex, mpsc}, thread, time::Duration,
|
2026-07-22 19:57:29 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use crossterm::{
|
2026-07-22 22:36:41 -05:00
|
|
|
ExecutableCommand,
|
|
|
|
|
event::{self, Event, KeyCode, KeyModifiers},
|
|
|
|
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
2026-07-22 19:57:29 -05:00
|
|
|
};
|
2026-07-22 22:15:59 -05:00
|
|
|
|
|
|
|
|
use figlet_rs::Toilet;
|
2026-07-22 22:36:41 -05:00
|
|
|
use notify_rust::Notification;
|
2026-07-22 19:57:29 -05:00
|
|
|
use ratatui::{
|
2026-07-22 22:36:41 -05:00
|
|
|
Terminal,
|
|
|
|
|
backend::CrosstermBackend,
|
|
|
|
|
layout::{Alignment, Constraint, Direction, Layout},
|
|
|
|
|
style::{Color, Style},
|
|
|
|
|
widgets::{Block, Borders, Clear, Paragraph},
|
2026-07-22 19:57:29 -05:00
|
|
|
};
|
|
|
|
|
|
2026-07-22 22:46:14 -05:00
|
|
|
const SECONDS_PER_MIN: u32 = 60;
|
2026-07-22 19:32:28 -05:00
|
|
|
|
2026-07-22 22:46:14 -05:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
|
|
enum TimerMode {
|
2026-07-22 19:32:28 -05:00
|
|
|
Work,
|
|
|
|
|
Break,
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 22:36:41 -05:00
|
|
|
fn send_desktop_notification(title: &str, body: &str) {
|
|
|
|
|
let title = title.to_string();
|
|
|
|
|
let body = body.to_string();
|
|
|
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
let _ = Notification::new()
|
|
|
|
|
.summary(&title)
|
|
|
|
|
.body(&body)
|
|
|
|
|
.appname("termato")
|
|
|
|
|
.show();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 19:32:28 -05:00
|
|
|
struct Termato {
|
2026-07-22 22:46:14 -05:00
|
|
|
work_mins: u32,
|
|
|
|
|
break_mins: u32,
|
|
|
|
|
mode: TimerMode,
|
2026-07-22 19:32:28 -05:00
|
|
|
is_running: bool,
|
2026-07-22 22:46:14 -05:00
|
|
|
duration_in_secs: u32,
|
|
|
|
|
time_remaining_in_sec: u32,
|
2026-07-22 22:24:48 -05:00
|
|
|
show_help: bool,
|
2026-07-22 23:16:44 -05:00
|
|
|
enable_notifications: bool,
|
2026-07-27 15:00:07 -05:00
|
|
|
enable_visualizer: bool,
|
2026-07-22 19:32:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Termato {
|
2026-07-22 22:46:14 -05:00
|
|
|
fn new(work_mins: u32, break_mins: u32) -> Self {
|
2026-07-22 19:32:28 -05:00
|
|
|
Termato {
|
|
|
|
|
work_mins,
|
|
|
|
|
break_mins,
|
2026-07-22 22:46:14 -05:00
|
|
|
mode: TimerMode::Work,
|
2026-07-22 19:32:28 -05:00
|
|
|
is_running: false,
|
|
|
|
|
duration_in_secs: work_mins * SECONDS_PER_MIN,
|
2026-07-22 22:43:10 -05:00
|
|
|
time_remaining_in_sec: work_mins * SECONDS_PER_MIN,
|
2026-07-22 22:24:48 -05:00
|
|
|
show_help: false,
|
2026-07-22 23:16:44 -05:00
|
|
|
enable_notifications: false,
|
2026-07-27 15:00:07 -05:00
|
|
|
enable_visualizer: false,
|
2026-07-22 19:32:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 23:16:44 -05:00
|
|
|
fn with_notifications(mut self, enable: bool) -> Self {
|
|
|
|
|
self.enable_notifications = enable;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-27 15:00:07 -05:00
|
|
|
fn with_visualizer(mut self, enable: bool) -> Self {
|
|
|
|
|
self.enable_visualizer = enable;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 19:57:29 -05:00
|
|
|
fn toggle_running(&mut self) {
|
2026-07-22 22:46:14 -05:00
|
|
|
self.is_running = !self.is_running;
|
2026-07-22 19:57:29 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-22 19:32:28 -05:00
|
|
|
fn toggle_mode(&mut self) {
|
2026-07-22 22:46:14 -05:00
|
|
|
self.mode = match self.mode {
|
|
|
|
|
TimerMode::Work => TimerMode::Break,
|
|
|
|
|
TimerMode::Break => TimerMode::Work,
|
|
|
|
|
};
|
|
|
|
|
self.duration_in_secs = match self.mode {
|
|
|
|
|
TimerMode::Work => self.work_mins * SECONDS_PER_MIN,
|
|
|
|
|
TimerMode::Break => self.break_mins * SECONDS_PER_MIN,
|
|
|
|
|
};
|
2026-07-22 19:32:28 -05:00
|
|
|
self.time_remaining_in_sec = self.duration_in_secs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn tick(&mut self) {
|
|
|
|
|
if self.is_running && self.time_remaining_in_sec > 0 {
|
|
|
|
|
self.time_remaining_in_sec -= 1;
|
|
|
|
|
|
|
|
|
|
if self.time_remaining_in_sec == 0 {
|
2026-07-22 23:16:44 -05:00
|
|
|
if self.enable_notifications {
|
|
|
|
|
match self.mode {
|
|
|
|
|
TimerMode::Work => send_desktop_notification("Work Done", "Time to take a break."),
|
|
|
|
|
TimerMode::Break => send_desktop_notification("Break Over", "Time to focus."),
|
|
|
|
|
}
|
2026-07-22 22:36:41 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-22 19:32:28 -05:00
|
|
|
self.toggle_mode();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-22 22:15:59 -05:00
|
|
|
|
|
|
|
|
fn reset(&mut self) {
|
|
|
|
|
self.is_running = false;
|
2026-07-22 22:46:14 -05:00
|
|
|
self.mode = TimerMode::Work;
|
2026-07-22 22:15:59 -05:00
|
|
|
self.duration_in_secs = self.work_mins * SECONDS_PER_MIN;
|
|
|
|
|
self.time_remaining_in_sec = self.duration_in_secs;
|
|
|
|
|
}
|
2026-07-22 22:24:48 -05:00
|
|
|
|
|
|
|
|
fn toggle_help(&mut self) {
|
|
|
|
|
self.show_help = !self.show_help;
|
|
|
|
|
}
|
2026-07-22 19:32:28 -05:00
|
|
|
}
|
|
|
|
|
|
2026-07-22 22:44:17 -05:00
|
|
|
struct TerminalGuard;
|
|
|
|
|
|
|
|
|
|
impl TerminalGuard {
|
|
|
|
|
fn new() -> Result<Self, io::Error> {
|
|
|
|
|
enable_raw_mode()?;
|
|
|
|
|
stdout().execute(EnterAlternateScreen)?;
|
|
|
|
|
Ok(TerminalGuard)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for TerminalGuard {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
let _ = disable_raw_mode();
|
|
|
|
|
let _ = stdout().execute(LeaveAlternateScreen);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 23:09:36 -05:00
|
|
|
// TODO: Refactor to an Elm like architecture
|
2026-07-22 19:57:29 -05:00
|
|
|
fn main() -> Result<(), io::Error> {
|
2026-07-22 22:48:27 -05:00
|
|
|
let args: Vec<String> = std::env::args().collect();
|
|
|
|
|
|
|
|
|
|
if args.iter().any(|arg| arg == "-h" || arg == "--help") {
|
2026-07-22 23:16:44 -05:00
|
|
|
println!("Usage: termato [options] [work_minutes] [break_minutes]");
|
|
|
|
|
println!();
|
|
|
|
|
println!("Options:");
|
|
|
|
|
println!(" -n, --notify Enable desktop notifications");
|
|
|
|
|
println!(" -h, --help Print this help message");
|
|
|
|
|
println!(" -v, --version Print the version number");
|
2026-07-27 15:00:07 -05:00
|
|
|
println!(" -z, --visualizer Display visualizer of playing audio");
|
2026-07-22 23:16:44 -05:00
|
|
|
println!();
|
2026-07-22 22:48:27 -05:00
|
|
|
println!("Defaults: work_minutes = 25, break_minutes = 5");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if args.iter().any(|arg| arg == "-v" || arg == "--version") {
|
|
|
|
|
println!("termato version {}", env!("CARGO_PKG_VERSION"));
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 22:44:17 -05:00
|
|
|
let _guard = TerminalGuard::new()?;
|
2026-07-22 22:15:59 -05:00
|
|
|
|
2026-07-22 19:57:29 -05:00
|
|
|
let backend = CrosstermBackend::new(stdout());
|
|
|
|
|
let mut terminal = Terminal::new(backend)?;
|
2026-07-22 22:15:59 -05:00
|
|
|
let font = Toilet::smblock().unwrap();
|
2026-07-22 22:48:27 -05:00
|
|
|
|
2026-07-27 15:00:07 -05:00
|
|
|
let enable_visualizer = args.iter().any(|arg| arg == "-z" || arg == "--visualizer");
|
2026-07-22 23:16:44 -05:00
|
|
|
let enable_notifications = args.iter().any(|arg| arg == "-n" || arg == "--notify");
|
2026-07-27 15:00:07 -05:00
|
|
|
|
2026-07-22 23:16:44 -05:00
|
|
|
let positional_args: Vec<&String> = args.iter()
|
|
|
|
|
.skip(1)
|
2026-07-27 15:00:07 -05:00
|
|
|
.filter(|arg| {
|
|
|
|
|
*arg != "-n" && *arg != "--notify" && *arg != "-vx" && *arg != "--visualizer"
|
|
|
|
|
})
|
2026-07-22 23:16:44 -05:00
|
|
|
.collect();
|
|
|
|
|
|
2026-07-27 15:00:07 -05:00
|
|
|
let work_mins = positional_args.first().and_then(|s| s.parse().ok()).unwrap_or(25);
|
2026-07-22 23:16:44 -05:00
|
|
|
let break_mins = positional_args.get(1).and_then(|s| s.parse().ok()).unwrap_or(5);
|
2026-07-27 15:00:07 -05:00
|
|
|
let mut termato = Termato::new(work_mins, break_mins)
|
|
|
|
|
.with_notifications(enable_notifications)
|
|
|
|
|
.with_visualizer(enable_visualizer);
|
2026-07-22 19:57:29 -05:00
|
|
|
|
|
|
|
|
let (tx, rx) = mpsc::channel();
|
2026-07-22 22:15:59 -05:00
|
|
|
|
2026-07-22 19:57:29 -05:00
|
|
|
thread::spawn(move || {
|
2026-07-22 19:32:28 -05:00
|
|
|
loop {
|
2026-07-22 19:57:29 -05:00
|
|
|
thread::sleep(Duration::from_secs(1));
|
|
|
|
|
if tx.send(()).is_err() {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-07-22 19:32:28 -05:00
|
|
|
}
|
2026-07-22 19:57:29 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
terminal.draw(|f| {
|
|
|
|
|
let size = f.area();
|
2026-07-22 22:24:48 -05:00
|
|
|
|
2026-07-22 19:57:29 -05:00
|
|
|
let minutes = termato.time_remaining_in_sec / SECONDS_PER_MIN;
|
|
|
|
|
let seconds = termato.time_remaining_in_sec % SECONDS_PER_MIN;
|
|
|
|
|
let time_str = format!("{:02}:{:02}", minutes, seconds);
|
2026-07-22 22:24:48 -05:00
|
|
|
|
2026-07-22 22:46:14 -05:00
|
|
|
let time_color = if !termato.is_running {
|
|
|
|
|
Color::Yellow
|
|
|
|
|
} else {
|
|
|
|
|
match termato.mode {
|
|
|
|
|
TimerMode::Work => Color::Red,
|
|
|
|
|
TimerMode::Break => Color::Green,
|
|
|
|
|
}
|
2026-07-22 22:15:59 -05:00
|
|
|
};
|
2026-07-22 19:57:29 -05:00
|
|
|
|
2026-07-22 22:15:59 -05:00
|
|
|
let big_time_text = if let Some(fig) = font.convert(&time_str) {
|
|
|
|
|
fig.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
time_str
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let text_height = big_time_text.lines().count() as u16;
|
|
|
|
|
|
|
|
|
|
let vertical_layout = Layout::default()
|
|
|
|
|
.direction(Direction::Vertical)
|
|
|
|
|
.constraints([
|
|
|
|
|
Constraint::Min(0),
|
|
|
|
|
Constraint::Length(text_height),
|
|
|
|
|
Constraint::Min(0),
|
|
|
|
|
])
|
|
|
|
|
.split(size);
|
|
|
|
|
|
|
|
|
|
let timer_paragraph = Paragraph::new(big_time_text)
|
|
|
|
|
.style(Style::default().fg(time_color))
|
2026-07-22 19:57:29 -05:00
|
|
|
.alignment(Alignment::Center);
|
|
|
|
|
|
2026-07-22 22:15:59 -05:00
|
|
|
f.render_widget(timer_paragraph, vertical_layout[1]);
|
2026-07-22 22:24:48 -05:00
|
|
|
|
|
|
|
|
if termato.show_help {
|
|
|
|
|
let popup_width = 44;
|
|
|
|
|
let popup_height = 7;
|
|
|
|
|
|
|
|
|
|
if size.width >= popup_width && size.height >= popup_height {
|
|
|
|
|
let popup_vertical = Layout::default()
|
|
|
|
|
.direction(Direction::Vertical)
|
|
|
|
|
.constraints([
|
|
|
|
|
Constraint::Length((size.height.saturating_sub(popup_height)) / 2),
|
|
|
|
|
Constraint::Length(popup_height),
|
|
|
|
|
Constraint::Min(1),
|
|
|
|
|
])
|
|
|
|
|
.split(size);
|
|
|
|
|
|
|
|
|
|
let popup_horizontal = Layout::default()
|
|
|
|
|
.direction(Direction::Horizontal)
|
|
|
|
|
.constraints([
|
|
|
|
|
Constraint::Length((size.width.saturating_sub(popup_width)) / 2),
|
|
|
|
|
Constraint::Length(popup_width),
|
|
|
|
|
Constraint::Min(1),
|
|
|
|
|
])
|
|
|
|
|
.split(popup_vertical[1]);
|
|
|
|
|
|
|
|
|
|
let popup_area = popup_horizontal[1];
|
|
|
|
|
|
|
|
|
|
let help_block = Block::default()
|
|
|
|
|
.title(" Controls ")
|
|
|
|
|
.borders(Borders::ALL)
|
|
|
|
|
.border_style(Style::default().fg(Color::DarkGray));
|
|
|
|
|
|
|
|
|
|
let help_content = Paragraph::new(
|
|
|
|
|
"[Space] Pause / Resume\n\
|
|
|
|
|
[S] Switch Mode\n\
|
|
|
|
|
[R] Reset Timer\n\
|
|
|
|
|
[?] Toggle Help\n\
|
|
|
|
|
[Q] Quit",
|
|
|
|
|
)
|
|
|
|
|
.block(help_block)
|
|
|
|
|
.style(Style::default().fg(Color::DarkGray))
|
|
|
|
|
.alignment(Alignment::Left);
|
|
|
|
|
|
|
|
|
|
f.render_widget(Clear, popup_area);
|
|
|
|
|
f.render_widget(help_content, popup_area);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-22 19:57:29 -05:00
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
if event::poll(Duration::from_millis(100))?
|
|
|
|
|
&& let Event::Key(key) = event::read()?
|
|
|
|
|
&& key.kind == event::KeyEventKind::Press
|
|
|
|
|
{
|
|
|
|
|
match key.code {
|
|
|
|
|
KeyCode::Char('q') | KeyCode::Char('Q') => break,
|
|
|
|
|
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => break,
|
|
|
|
|
KeyCode::Char(' ') => termato.toggle_running(),
|
2026-07-22 22:15:59 -05:00
|
|
|
KeyCode::Char('s') | KeyCode::Char('S') => termato.toggle_mode(),
|
|
|
|
|
KeyCode::Char('r') | KeyCode::Char('R') => termato.reset(),
|
2026-07-22 22:24:48 -05:00
|
|
|
KeyCode::Char('?') => termato.toggle_help(),
|
2026-07-22 19:57:29 -05:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if rx.try_recv().is_ok() {
|
|
|
|
|
termato.tick();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2026-07-22 19:32:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-27 15:00:07 -05:00
|
|
|
fn termato_new_when_called_it_should_return_expected_starting_state() {
|
2026-07-22 22:43:10 -05:00
|
|
|
let result = Termato::new(25, 5);
|
2026-07-22 19:32:28 -05:00
|
|
|
|
2026-07-22 22:43:10 -05:00
|
|
|
assert_eq!(result.work_mins, 25);
|
|
|
|
|
assert_eq!(result.break_mins, 5);
|
2026-07-22 22:46:14 -05:00
|
|
|
assert_eq!(result.mode, TimerMode::Work);
|
2026-07-22 19:32:28 -05:00
|
|
|
assert!(!result.is_running);
|
2026-07-22 22:43:10 -05:00
|
|
|
assert_eq!(result.duration_in_secs, 1500);
|
|
|
|
|
assert_eq!(result.time_remaining_in_sec, 1500);
|
2026-07-27 15:00:07 -05:00
|
|
|
assert!(!result.show_help);
|
|
|
|
|
assert!(!result.enable_notifications);
|
|
|
|
|
assert!(!result.enable_visualizer);
|
2026-07-22 19:32:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_tick_when_called_and_not_running_it_should_do_nothing() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
|
|
|
|
|
termato.tick();
|
|
|
|
|
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 60);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_tick_when_called_and_no_time_remaining_it_should_do_nothing() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
termato.is_running = true;
|
|
|
|
|
termato.time_remaining_in_sec = 0;
|
|
|
|
|
|
|
|
|
|
termato.tick();
|
|
|
|
|
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_tick_when_running_and_time_reamining_it_should_reduce_remaining_by_one() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
termato.is_running = true;
|
|
|
|
|
|
|
|
|
|
termato.tick();
|
|
|
|
|
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 59);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_tick_when_running_on_work_and_time_remaining_reaches_zero_it_should_toggle_to_break() {
|
|
|
|
|
let mut termato = Termato::new(1, 2);
|
|
|
|
|
termato.is_running = true;
|
|
|
|
|
termato.time_remaining_in_sec = 1;
|
|
|
|
|
|
|
|
|
|
termato.tick();
|
|
|
|
|
|
2026-07-22 22:46:14 -05:00
|
|
|
assert_eq!(termato.mode, TimerMode::Break);
|
2026-07-22 19:32:28 -05:00
|
|
|
assert_eq!(termato.duration_in_secs, 120);
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 120);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_tick_when_running_on_break_and_time_remaining_reaches_zero_it_should_toggle_to_work() {
|
|
|
|
|
let mut termato = Termato::new(2, 1);
|
2026-07-22 22:46:14 -05:00
|
|
|
termato.mode = TimerMode::Break;
|
2026-07-22 19:32:28 -05:00
|
|
|
termato.is_running = true;
|
|
|
|
|
termato.time_remaining_in_sec = 1;
|
|
|
|
|
|
|
|
|
|
termato.tick();
|
|
|
|
|
|
2026-07-22 22:46:14 -05:00
|
|
|
assert_eq!(termato.mode, TimerMode::Work);
|
2026-07-22 19:32:28 -05:00
|
|
|
assert_eq!(termato.duration_in_secs, 120);
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 120);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-07-22 22:46:14 -05:00
|
|
|
fn termato_toggle_mode_when_called_it_should_toggle_the_mode() {
|
2026-07-22 19:32:28 -05:00
|
|
|
let mut termato = Termato::new(2, 1);
|
|
|
|
|
|
|
|
|
|
termato.toggle_mode();
|
2026-07-22 22:46:14 -05:00
|
|
|
assert_eq!(termato.mode, TimerMode::Break);
|
|
|
|
|
assert_eq!(termato.duration_in_secs, 60);
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 60);
|
2026-07-22 19:32:28 -05:00
|
|
|
|
2026-07-22 22:46:14 -05:00
|
|
|
termato.toggle_mode();
|
|
|
|
|
assert_eq!(termato.mode, TimerMode::Work);
|
2026-07-22 19:32:28 -05:00
|
|
|
assert_eq!(termato.duration_in_secs, 120);
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 120);
|
|
|
|
|
}
|
2026-07-22 19:57:29 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_toggle_running_when_called_and_already_running_it_should_pause() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
termato.is_running = true;
|
|
|
|
|
|
|
|
|
|
termato.toggle_running();
|
|
|
|
|
|
|
|
|
|
assert!(!termato.is_running)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_toggle_running_when_called_and_already_paused_it_should_run() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
|
|
|
|
|
termato.toggle_running();
|
|
|
|
|
|
|
|
|
|
assert!(termato.is_running)
|
|
|
|
|
}
|
2026-07-22 22:15:59 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_rest_when_called_it_should_reset_the_apps_state() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
termato.is_running = true;
|
2026-07-22 22:46:14 -05:00
|
|
|
termato.mode = TimerMode::Break;
|
2026-07-22 22:15:59 -05:00
|
|
|
termato.duration_in_secs = 1;
|
|
|
|
|
termato.time_remaining_in_sec = 1;
|
|
|
|
|
|
|
|
|
|
termato.reset();
|
|
|
|
|
|
|
|
|
|
assert!(!termato.is_running);
|
2026-07-22 22:46:14 -05:00
|
|
|
assert_eq!(termato.mode, TimerMode::Work);
|
2026-07-22 22:15:59 -05:00
|
|
|
assert_eq!(termato.duration_in_secs, 60);
|
|
|
|
|
assert_eq!(termato.time_remaining_in_sec, 60);
|
|
|
|
|
}
|
2026-07-22 22:24:48 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_toggle_help_when_called_it_should_toggle_help() {
|
|
|
|
|
let mut termato = Termato::new(1, 1);
|
|
|
|
|
|
|
|
|
|
termato.toggle_help();
|
|
|
|
|
|
|
|
|
|
assert!(termato.show_help);
|
|
|
|
|
|
|
|
|
|
termato.toggle_help();
|
|
|
|
|
|
|
|
|
|
assert!(!termato.show_help);
|
|
|
|
|
}
|
2026-07-22 23:16:44 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn termato_notifications_when_called_it_should_verify_opt_in_behavior() {
|
|
|
|
|
let result = Termato::new(25, 5);
|
|
|
|
|
assert!(!result.enable_notifications);
|
|
|
|
|
|
|
|
|
|
let result = result.with_notifications(true);
|
|
|
|
|
assert!(result.enable_notifications);
|
|
|
|
|
}
|
2026-07-22 18:25:02 -05:00
|
|
|
}
|