From d5955ddb8f054fc1790fedb9814da2120fd35bda Mon Sep 17 00:00:00 2001 From: mdrokz Date: Fri, 6 Oct 2023 11:26:52 +0530 Subject: [PATCH] Implement base integration tests (#16) --- Cargo.toml | 4 +- src/main.rs | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/runner.rs | 4 +- 3 files changed, 127 insertions(+), 3 deletions(-) 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 diff --git a/src/main.rs b/src/main.rs index 3583f1d..b0876f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,3 +163,125 @@ 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, + uppercase: None, + uppercase_ratio: 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, + uppercase: None, + uppercase_ratio: 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 00fb710..109b250 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -196,7 +196,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()) @@ -424,7 +424,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;