From 371bfee16507d35ad99eaa55f9da6ff4017c5bff Mon Sep 17 00:00:00 2001 From: mdrokz Date: Thu, 5 Oct 2023 13:38:47 +0530 Subject: [PATCH 1/3] dep: move assert_cmd to dev_dependencies --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e3a0268..badafea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] anyhow = "1.0.75" -assert_cmd = "2.0.12" clap = { version = "4.4.3", features = ["derive"] } crossterm = "0.27.0" dirs = "5.0.1" @@ -18,3 +17,6 @@ serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" tempfile = "3.8.0" tui = "0.19.0" + +[dev-dependencies] +assert_cmd = "2.0.12" \ No newline at end of file From 78561ffd1458ca588c6532260ddf9e91542aedbe Mon Sep 17 00:00:00 2001 From: mdrokz Date: Thu, 5 Oct 2023 13:39:45 +0530 Subject: [PATCH 2/3] feat: write integration tests using `TestBackend` --- src/main.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/runner.rs | 4 +- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index a8312b3..18dc0a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -144,3 +144,121 @@ fn restore_terminal( Ok(()) } + +#[cfg(test)] +mod tests { + use std::{io::Write, time::Instant}; + + use anyhow::{Context, Result}; + use predicates::Predicate; + use tui::{backend::TestBackend, buffer::Buffer, Frame, Terminal}; + + use crate::{ + args::Args, + config::Config, + expected_input::ExpectedInput, + runner::{FrameWrapper, Runner}, + }; + + fn configure_terminal() -> Result, anyhow::Error> { + let backend = TestBackend::new(400, 400); + let terminal = Terminal::new(backend).context("Unable to create terminal")?; + + Ok(terminal) + } + + fn extract_text_from_buffer(buffer: &Buffer) -> String { + let mut text = String::new(); + + for y in 0..buffer.area.height { + for x in 0..buffer.area.height { + let cell = buffer.get(x, y); + text.push_str(&cell.symbol); + } + text.push('\n'); + } + + text + } + + fn setup_terminal(args: Args) -> Result<(Config, ExpectedInput, Terminal)> { + let config_file_path = dirs::home_dir() + .context("Unable to get home directory")? + .join(".config") + .join("donkeytype") + .join("donkeytype-config.json"); + + let config = Config::new(args, config_file_path).context("Unable to create config")?; + let expected_input = + ExpectedInput::new(&config).context("Unable to create expected input")?; + let terminal = configure_terminal().context("Unable to configure terminal")?; + + Ok((config, expected_input, terminal)) + } + + #[test] + fn should_print_default_expected_input() -> Result<()> { + let mut temp_dict_file = + tempfile::NamedTempFile::new().expect("Unable to create temp file"); + temp_dict_file + .write_all(r#"hello world"#.as_bytes()) + .expect("Unable to write to temp file"); + + let args = Args { + dictionary_path: Some(temp_dict_file.path().display().to_string()), + duration: None, + numbers: None, + numbers_ratio: None, + }; + + let (config, expected_input, mut terminal) = setup_terminal(args)?; + + let mut app = Runner::new(config, expected_input); + let start_time = Instant::now(); + + terminal + .draw(|f: &mut Frame| { + let mut frame_wrapper = FrameWrapper::new(f); + app.render(&mut frame_wrapper, start_time.elapsed().as_secs()); + }) + .context("Unable to draw in terminal")?; + + let text = extract_text_from_buffer(terminal.backend().buffer()); + + let predicate = predicates::str::contains("hello world"); + + assert_eq!(true, predicate.eval(&text)); + + Ok(()) + } + + #[test] + fn should_print_help_message_for_normal_mode() -> Result<()> { + let args = Args { + dictionary_path: None, + duration: None, + numbers: None, + numbers_ratio: None, + }; + + let (config, expected_input, mut terminal) = setup_terminal(args)?; + + let mut app = Runner::new(config, expected_input); + let start_time = Instant::now(); + + terminal + .draw(|f: &mut Frame| { + let mut frame_wrapper = FrameWrapper::new(f); + app.render(&mut frame_wrapper, start_time.elapsed().as_secs()); + }) + .context("Unable to draw in terminal")?; + + let text = extract_text_from_buffer(terminal.backend().buffer()); + + let predicate = predicates::str::contains("press 'e' to start the test, press 'q' to quit"); + + assert_eq!(true, predicate.eval(&text)); + + Ok(()) + } +} diff --git a/src/runner.rs b/src/runner.rs index 7a87218..cba3472 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -171,7 +171,7 @@ impl Runner { /// There are two areas being rendered, /// input area - where user input and expected input is displayed, /// and info arae - where help message and time remaining is rendered. - fn render(&mut self, frame: &mut impl FrameWrapperInterface, time_elapsed: u64) { + pub fn render(&mut self, frame: &mut impl FrameWrapperInterface, time_elapsed: u64) { let areas = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(1), Constraint::Length(1)].as_ref()) @@ -399,7 +399,7 @@ impl Runner { /// Used for generating mocks using `mockall` crate #[automock] -trait FrameWrapperInterface { +pub trait FrameWrapperInterface { fn render_widget(&mut self, widget: W, area: Rect); fn set_cursor(&mut self, x: u16, y: u16); fn size(&self) -> Rect; From e794252e7b23ef2b4515be3a99a343e3719d55ee Mon Sep 17 00:00:00 2001 From: mdrokz Date: Fri, 6 Oct 2023 09:49:41 +0530 Subject: [PATCH 3/3] fix: errors in test cases --- src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.rs b/src/main.rs index aa511b3..b0876f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -227,6 +227,8 @@ mod tests { dictionary_path: Some(temp_dict_file.path().display().to_string()), duration: None, numbers: None, + uppercase: None, + uppercase_ratio: None, numbers_ratio: None, }; @@ -256,6 +258,8 @@ mod tests { let args = Args { dictionary_path: None, duration: None, + uppercase: None, + uppercase_ratio: None, numbers: None, numbers_ratio: None, };