diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/rust/app.rs | 205 | ||||
| -rw-r--r-- | src/main/rust/main.rs | 178 |
2 files changed, 256 insertions, 127 deletions
diff --git a/src/main/rust/app.rs b/src/main/rust/app.rs new file mode 100644 index 0000000..d6e84e1 --- /dev/null +++ b/src/main/rust/app.rs @@ -0,0 +1,205 @@ +use crate::store::{Context, Store}; +use miette::{Diagnostic, Result}; +use thiserror::Error; + +#[derive(Default)] +pub struct App { + context: Context, + exit: bool, +} + +#[derive(Diagnostic, Debug, Error, PartialEq)] +#[error("An overflow error occurred")] +#[diagnostic()] +pub struct OverflowError { + #[help] + pub _msg: String, +} + +impl App { + /// Build context in App and pass it forward + pub fn init_context(&mut self) -> Result<&mut Self> { + let store = self.context.unwrap_store(); + + store.init()?; + Ok(self) + } + + pub fn get_exit_status(&self) -> bool { + self.exit + } + + pub fn exit(&mut self) -> Result<()> { + self.exit = true; + Ok(()) + } + + pub fn decrement_counter(&mut self) -> Result<()> { + let store = self.context.unwrap_store(); + let count = store.get_counter()?; + + if count == 0 { + Err(OverflowError { + _msg: "Cannot count below 0".into(), + } + .into()) + } else { + store.set_counter(count - 1)?; + Ok(()) + } + } + + pub fn increment_counter(&mut self) -> Result<()> { + let store = self.context.unwrap_store(); + let count = store.get_counter()?; + + if count == u8::MAX { + Err(OverflowError { + _msg: "Cannot count above u8::MAX".into(), + } + .into()) + } else { + store.set_counter(count + 1)?; + Ok(()) + } + } + + pub fn display_counter(&self) -> String { + self.context + .get_store() + .get_counter() + .map(|u| u.to_string()) + .unwrap_or("Error".into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::store::ContextError; + + fn get_count(app: &App) -> Result<u8, ContextError> { + app.context.get_store().get_counter() + } + + #[test] + fn display_counter_inited() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + + let count = app.display_counter(); + + assert_eq!(count, "0"); + } + + #[test] + fn display_counter_noninited() { + let app = App::default(); + + let count = app.display_counter(); + + assert_eq!(count, "Error"); + } + + #[test] + fn increment_decrement() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + + let res = app.increment_counter(); + assert!(res.is_ok()); + assert_eq!(get_count(&app).unwrap(), 1); + + let res = app.decrement_counter(); + assert!(res.is_ok()); + assert_eq!(get_count(&app).unwrap(), 0); + } + + #[test] + fn increment_n() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let n = 8; + + for i in 1..=n { + let res = app.increment_counter(); + assert!(res.is_ok()); + assert_eq!(get_count(&app).unwrap(), i); + } + assert_eq!(get_count(&app).unwrap(), n); + } + + #[test] + fn increment_n_decrement() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let n = 8; + + for i in 1..=n { + let res = app.increment_counter(); + assert!(res.is_ok()); + assert_eq!(get_count(&app).unwrap(), i); + } + assert_eq!(get_count(&app).unwrap(), n); + + let res = app.decrement_counter(); + assert!(res.is_ok()); + assert_eq!(get_count(&app).unwrap(), n - 1); + } + + #[test] + fn exit() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + + let res = app.exit(); + + assert!(res.is_ok()); + assert!(app.exit); + } + + #[test] + fn decrement_counter_error() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let res = app.decrement_counter(); + + assert!(res.is_err()); + let res_err = res.unwrap_err(); + let casted_val = res_err + .downcast_ref::<OverflowError>() + .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 increment_counter_error() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + assert!(app.context.unwrap_store().set_counter(u8::MAX).is_ok()); + let res = app.increment_counter(); + + assert!(res.is_err()); + let res_err = res.unwrap_err(); + let casted_val = res_err + .downcast_ref::<OverflowError>() + .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, + }, + ); + } +} diff --git a/src/main/rust/main.rs b/src/main/rust/main.rs index 2b77a18..e42fde9 100644 --- a/src/main/rust/main.rs +++ b/src/main/rust/main.rs @@ -1,7 +1,9 @@ +mod app; pub mod store; +use app::App; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; -use miette::{Diagnostic, IntoDiagnostic, Result}; +use miette::{IntoDiagnostic, Result}; use ratatui::{ DefaultTerminal, Frame, buffer::Buffer, @@ -11,41 +13,27 @@ use ratatui::{ text::{Line, Text}, widgets::{Block, Paragraph, Widget}, }; -use store::{Context, Store}; -use thiserror::Error; - -#[derive(Default)] -pub struct App { - context: Context, - exit: bool, -} - -#[derive(Diagnostic, Debug, Error, PartialEq)] -#[error("An overflow error occurred")] -#[diagnostic()] -pub struct OverflowError { - #[help] - _msg: String, -} #[cfg(not(tarpaulin_include))] fn main() -> Result<()> { - ratatui::run(|term| App::default().init_context()?.run(term)) + ratatui::run(|term| { + let mut ctx = Runtime { + app: App::default(), + }; + ctx.app.init_context()?; + ctx.run(term) + }) } -impl App { - /// Build context in App and pass it forward - pub fn init_context(&mut self) -> Result<&mut Self> { - let store = self.context.unwrap_store(); - - store.init()?; - Ok(self) - } +struct Runtime { + app: App, +} +impl Runtime { /// Run the app's main loop until the user quits #[cfg(not(tarpaulin_include))] pub fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> { - while !self.exit { + while !self.app.get_exit_status() { terminal.draw(|frame| self.draw(frame)).into_diagnostic()?; self.handle_events()?; } @@ -72,59 +60,16 @@ impl App { /// Process user input and run associated app functions fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> { match key_event.code { - KeyCode::Char('q') => self.exit()?, - KeyCode::Left => self.decrement_counter()?, - KeyCode::Right => self.increment_counter()?, + KeyCode::Char('q') => self.app.exit()?, + KeyCode::Left => self.app.decrement_counter()?, + KeyCode::Right => self.app.increment_counter()?, _ => {} }; Ok(()) } - - fn exit(&mut self) -> Result<()> { - self.exit = true; - Ok(()) - } - - fn decrement_counter(&mut self) -> Result<()> { - let store = self.context.unwrap_store(); - let count = store.get_counter()?; - - if count == 0 { - Err(OverflowError { - _msg: "Cannot count below 0".into(), - } - .into()) - } else { - store.set_counter(count - 1)?; - Ok(()) - } - } - - fn increment_counter(&mut self) -> Result<()> { - let store = self.context.unwrap_store(); - let count = store.get_counter()?; - - if count == u8::MAX { - Err(OverflowError { - _msg: "Cannot count above u8::MAX".into(), - } - .into()) - } else { - store.set_counter(count + 1)?; - Ok(()) - } - } - - fn display_counter(&self) -> String { - self.context - .get_store() - .get_counter() - .map(|u| u.to_string()) - .unwrap_or("Error".into()) - } } -impl Widget for &App { +impl Widget for &Runtime { fn render(self, area: Rect, buf: &mut Buffer) { let title = Line::from(" Counter App Tutorial ".bold()); let instructions = Line::from(vec![ @@ -142,7 +87,7 @@ impl Widget for &App { let counter_text = Text::from(vec![Line::from(vec![ "Value: ".into(), - self.display_counter().yellow(), + self.app.display_counter().yellow(), ])]); Paragraph::new(counter_text) @@ -154,42 +99,24 @@ impl Widget for &App { #[cfg(test)] mod tests { - use crate::store::ContextError; use super::*; + use app::OverflowError; use ratatui::style::Style; - fn get_count(app: &App) -> Result<u8, ContextError> { - app.context.get_store().get_counter() - } - - #[test] - fn display_counter_inited() { + fn default_runtime() -> Runtime { let mut app = App::default(); assert!(app.init_context().is_ok()); - - let count = app.display_counter(); - - assert_eq!(count, "0"); - } - - #[test] - fn display_counter_noninited() { - let app = App::default(); - - let count = app.display_counter(); - - assert_eq!(count, "Error"); + Runtime { app } } #[test] fn render() { - let mut app = App::default(); - assert!(app.init_context().is_ok()); + let ctx = default_runtime(); let mut buf = Buffer::empty(Rect::new(0, 0, 50, 4)); - app.render(buf.area, &mut buf); + ctx.render(buf.area, &mut buf); let mut expected = Buffer::with_lines(vec![ "┏━━━━━━━━━━━━━ Counter App Tutorial ━━━━━━━━━━━━━┓", @@ -212,64 +139,59 @@ mod tests { #[test] fn handle_key_event_inc_dec() { - let mut app = App::default(); - assert!(app.init_context().is_ok()); + let mut ctx = default_runtime(); - let res = app.handle_key_event(KeyCode::Right.into()); + let res = ctx.handle_key_event(KeyCode::Right.into()); assert!(res.is_ok()); - assert_eq!(get_count(&app).unwrap(), 1); + assert_eq!(ctx.app.display_counter(), "1"); - let res = app.handle_key_event(KeyCode::Left.into()); + let res = ctx.handle_key_event(KeyCode::Left.into()); assert!(res.is_ok()); - assert_eq!(get_count(&app).unwrap(), 0); + assert_eq!(ctx.app.display_counter(), "0"); } #[test] fn handle_key_event_inc_n() { - let mut app = App::default(); - assert!(app.init_context().is_ok()); + let mut ctx = default_runtime(); let n = 8; for i in 1..=n { - let res = app.handle_key_event(KeyCode::Right.into()); + let res = ctx.handle_key_event(KeyCode::Right.into()); assert!(res.is_ok()); - assert_eq!(get_count(&app).unwrap(), i); + assert_eq!(ctx.app.display_counter(), i.to_string()); } - assert_eq!(get_count(&app).unwrap(), n); + assert_eq!(ctx.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 mut ctx = default_runtime(); let n = 8; for i in 1..=n { - let res = app.handle_key_event(KeyCode::Right.into()); + let res = ctx.handle_key_event(KeyCode::Right.into()); assert!(res.is_ok()); - assert_eq!(get_count(&app).unwrap(), i); + assert_eq!(ctx.app.display_counter(), i.to_string()); } - assert_eq!(get_count(&app).unwrap(), n); + assert_eq!(ctx.app.display_counter(), n.to_string()); - let res = app.handle_key_event(KeyCode::Left.into()); + let res = ctx.handle_key_event(KeyCode::Left.into()); assert!(res.is_ok()); - assert_eq!(get_count(&app).unwrap(), n - 1); + assert_eq!(ctx.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 = app.handle_key_event(KeyCode::Char('q').into()); + let mut ctx = default_runtime(); + let res = ctx.handle_key_event(KeyCode::Char('q').into()); assert!(res.is_ok()); - assert!(app.exit); + assert!(ctx.app.get_exit_status()); } #[test] fn handle_key_event_dec_error() { - let mut app = App::default(); - assert!(app.init_context().is_ok()); - let res = app.handle_key_event(KeyCode::Left.into()); + let mut ctx = default_runtime(); + let res = ctx.handle_key_event(KeyCode::Left.into()); assert!(res.is_err()); let res_err = res.unwrap_err(); @@ -289,10 +211,12 @@ mod tests { #[test] fn handle_key_event_inc_error() { - let mut app = App::default(); - assert!(app.init_context().is_ok()); - assert!(app.context.unwrap_store().set_counter(u8::MAX).is_ok()); - let res = app.handle_key_event(KeyCode::Right.into()); + let mut ctx = default_runtime(); + // no pub set_counter exposed, so we manually set this + for _ in 0..u8::MAX { + assert!(ctx.app.increment_counter().is_ok()); + } + let res = ctx.handle_key_event(KeyCode::Right.into()); assert!(res.is_err()); let res_err = res.unwrap_err(); |
