mod app; pub mod store; mod ui; use app::App; use miette::{IntoDiagnostic, Result}; use ratatui::{Terminal, backend::Backend}; use ratatui::crossterm::{ event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, }, execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use ratatui::prelude::CrosstermBackend; #[cfg(not(tarpaulin_include))] fn main() -> Result<()> { enable_raw_mode().into_diagnostic()?; let mut stdout = std::io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture).into_diagnostic()?; let backend = CrosstermBackend::new(stdout); let mut term = Terminal::new(backend).into_diagnostic()?; let mut app = App::default(); app.init_context()?; // could be error, so don't unwrap; let term restore first let run = run_app(&mut term, &mut app); // restore terminal disable_raw_mode().into_diagnostic()?; execute!( term.backend_mut(), LeaveAlternateScreen, DisableMouseCapture ) .into_diagnostic()?; term.show_cursor().into_diagnostic()?; run?; Ok(()) } #[cfg(not(tarpaulin_include))] fn run_app(term: &mut Terminal, app: &mut App) -> Result<()> { while !app.get_exit_status() { term.draw(|frame| ui::draw(frame, app)) .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "idk")) .into_diagnostic()?; match event::read().into_diagnostic()? { // it's important to check that the event is a key press event as // crossterm also emits key release and repeat events on Windows. Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { handle_key_event(app, key_event)?; } _ => {} }; } Ok(()) } /// Process user input and run associated app functions fn handle_key_event(app: &mut App, key_event: KeyEvent) -> Result<()> { match key_event.code { KeyCode::Char('q') => app.exit()?, KeyCode::Left => app.decrement_counter()?, KeyCode::Right => app.increment_counter()?, _ => {} }; Ok(()) } #[cfg(test)] mod tests { use super::*; use app::OverflowError; #[test] fn handle_key_event_inc_dec() { let mut app = App::default(); assert!(app.init_context().is_ok()); let res = handle_key_event(&mut app, KeyCode::Right.into()); assert!(res.is_ok()); assert_eq!(app.display_counter(), "1"); let res = handle_key_event(&mut app, KeyCode::Left.into()); assert!(res.is_ok()); assert_eq!(app.display_counter(), "0"); } #[test] fn handle_key_event_inc_n() { let mut app = App::default(); assert!(app.init_context().is_ok()); let n = 8; for i in 1..=n { let res = handle_key_event(&mut app, KeyCode::Right.into()); assert!(res.is_ok()); assert_eq!(app.display_counter(), i.to_string()); } assert_eq!(app.display_counter(), n.to_string()); } #[test] fn handle_key_event_inc_n_dec() { let mut app = App::default(); assert!(app.init_context().is_ok()); let n = 8; for i in 1..=n { let res = handle_key_event(&mut app, KeyCode::Right.into()); assert!(res.is_ok()); assert_eq!(app.display_counter(), i.to_string()); } assert_eq!(app.display_counter(), n.to_string()); let res = handle_key_event(&mut app, KeyCode::Left.into()); assert!(res.is_ok()); assert_eq!(app.display_counter(), (n - 1).to_string()); } #[test] fn handle_key_event_exit() { let mut app = App::default(); assert!(app.init_context().is_ok()); let res = handle_key_event(&mut app, KeyCode::Char('q').into()); assert!(res.is_ok()); assert!(app.get_exit_status()); } #[test] fn handle_key_event_dec_error() { let mut app = App::default(); assert!(app.init_context().is_ok()); let res = handle_key_event(&mut app, KeyCode::Left.into()); assert!(res.is_err()); let res_err = res.unwrap_err(); let casted_val = res_err .downcast_ref::() .expect("something bad occurred"); let expected_err_msg: String = "Cannot count below 0".into(); assert_eq!(casted_val._msg, expected_err_msg); assert_eq!( *casted_val, OverflowError { _msg: expected_err_msg, }, ); } #[test] fn handle_key_event_inc_error() { let mut app = App::default(); assert!(app.init_context().is_ok()); // no pub set_counter exposed, so we manually set this for _ in 0..u8::MAX { assert!(app.increment_counter().is_ok()); } let res = handle_key_event(&mut app, KeyCode::Right.into()); assert!(res.is_err()); let res_err = res.unwrap_err(); let casted_val = res_err .downcast_ref::() .expect("something bad occurred"); let expected_err_msg: String = "Cannot count above u8::MAX".into(); assert_eq!(casted_val._msg, expected_err_msg); assert_eq!( *casted_val, OverflowError { _msg: expected_err_msg, }, ); } }