From 514c600bb14393b0b9512b57be8cd55fe0834cc9 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 17 Jun 2023 12:35:17 +0700 Subject: [PATCH] Integration tests covering 6 main scenarios Covers - Add - Select - Select Top/Bottom - Move - Edit - Delete --- src/app.rs | 3 - src/app/tests.rs | 1 - src/db.rs | 2 +- src/ui.rs | 31 ++--- tests/app_tests.rs | 322 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 334 insertions(+), 25 deletions(-) delete mode 100644 src/app/tests.rs create mode 100644 tests/app_tests.rs diff --git a/src/app.rs b/src/app.rs index c2fd3d6..a3373b6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,9 +7,6 @@ use tui_textarea::TextArea; use crate::db; -#[cfg(test)] -mod tests; - #[derive(Debug, Serialize, Deserialize)] pub struct Column { pub id: i64, diff --git a/src/app/tests.rs b/src/app/tests.rs deleted file mode 100644 index 35ba390..0000000 --- a/src/app/tests.rs +++ /dev/null @@ -1 +0,0 @@ -// use super::*; diff --git a/src/db.rs b/src/db.rs index 6d36ab4..bd3ea66 100644 --- a/src/db.rs +++ b/src/db.rs @@ -3,7 +3,7 @@ use anyhow::Error; use rusqlite::{params, Connection, Result}; use std::ops::{Deref, DerefMut}; -pub struct DBConn(Connection); +pub struct DBConn(pub Connection); impl DerefMut for DBConn { fn deref_mut(&mut self) -> &mut Self::Target { diff --git a/src/ui.rs b/src/ui.rs index 34be8b3..314a743 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -35,8 +35,6 @@ fn draw_tasks(f: &mut Frame<'_, B>, area: Rect, state: &State<'_>) { span = Span::raw(&task.title); } span.style = style; - // TODO: This is unoptimized, we can actually construct the list - // inside a single ListItem it seems ListItem::new(vec![Spans::from(span)]) }) .collect(); @@ -202,24 +200,17 @@ fn draw_project_stats(f: &mut Frame<'_, B>, area: Rect, state: &mut let c4_len = state.columns[3].tasks.len(); let tocomplete_total = c1_len + c2_len + c3_len; let percentage = (c3_len as f32 / tocomplete_total as f32 * 100.0) as u8; - let list = List::new( - vec![ - ListItem::new(vec![ - Spans::from("Tasks per Column:"), - Spans::from(format!(" Todo ({})", c1_len)), - Spans::from(format!(" In Progress ({})", c2_len)), - Spans::from(format!(" Done ({})", c3_len)), - Spans::from(format!(" Ideas ({})", c4_len)), - Spans::from( - format!( - "Progress: {} / {} - {}%", - c3_len, - tocomplete_total, - percentage - )) - ] - )] - ) + let list = List::new(vec![ListItem::new(vec![ + Spans::from("Tasks per Column:"), + Spans::from(format!(" Todo ({})", c1_len)), + Spans::from(format!(" In Progress ({})", c2_len)), + Spans::from(format!(" Done ({})", c3_len)), + Spans::from(format!(" Ideas ({})", c4_len)), + Spans::from(format!( + "Progress: {} / {} - {}%", + c3_len, tocomplete_total, percentage + )), + ])]) .block(block); f.render_widget(list, area); diff --git a/tests/app_tests.rs b/tests/app_tests.rs new file mode 100644 index 0000000..0c8c947 --- /dev/null +++ b/tests/app_tests.rs @@ -0,0 +1,322 @@ +// - [X] Adding +// - [X] Selecting general +// - [X] Selecting top/bottom +// - [X] Moving +// - [X] Editing +// - [X] Deleting +#[cfg(test)] +mod app_tests { + use anyhow::Error; + use kanban_tui::State; + use rusqlite::Connection; + + fn create_connection() -> Result { + let mut conn = Connection::open_in_memory()?; + + let migrations = include_str!("../sql/migrations.sql"); + let migrations: Vec<&str> = migrations.split(';').collect(); + let tx = conn.transaction()?; + for m in migrations { + if !m.trim().is_empty() { + tx.execute(m, ())?; + } + } + tx.commit()?; + Ok(conn) + } + + #[test] + fn it_adds_tasks_to_different_columns() -> Result<(), Error> { + let mut state = State::new(create_connection()?)?; + + state.add_new_task(String::from("T1"), String::from("D1"))?; + state.add_new_task(String::from("T2"), String::from("D2"))?; + state.select_next_column()?; + state.select_next_column()?; + state.add_new_task(String::from("T3"), String::from("D3"))?; + state.select_previous_column()?; + state.add_new_task(String::from("T4"), String::from("D4"))?; + + assert_eq!(state.columns[0].tasks.len(), 2); + assert_eq!(state.columns[1].tasks.len(), 1); + assert_eq!(state.columns[2].tasks.len(), 1); + assert_eq!(&state.columns[0].tasks[0].title, "T1"); + assert_eq!(&state.columns[0].tasks[1].description, "D2"); + assert_eq!(&state.columns[1].tasks[0].title, "T4"); + assert_eq!(&state.columns[2].tasks[0].description, "D3"); + + // Reload the data from the database then rerun the asserts to + // make sure everything was saved correctly + let state = State::new(state.db_conn.0)?; + + assert_eq!(state.columns[0].tasks.len(), 2); + assert_eq!(state.columns[1].tasks.len(), 1); + assert_eq!(state.columns[2].tasks.len(), 1); + assert_eq!(&state.columns[0].tasks[0].title, "T1"); + assert_eq!(&state.columns[0].tasks[1].description, "D2"); + assert_eq!(&state.columns[1].tasks[0].title, "T4"); + assert_eq!(&state.columns[2].tasks[0].description, "D3"); + + Ok(()) + } + + #[test] + fn it_selects_the_correct_tasks_and_stays_inbound() -> Result<(), Error> { + let mut state = State::new(create_connection()?)?; + + state.move_task_up()?; + state.move_task_up()?; + state.move_task_down()?; + state.move_task_down()?; + for _ in 0..10 { + state.select_next_column()?; + } + for _ in 0..10 { + state.select_previous_column()?; + } + state.add_new_task(String::from("T1"), String::from("D1"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.add_new_task(String::from("T2"), String::from("D2"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.select_previous_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + for _ in 0..6 { + state.select_next_column()?; + } + state.select_previous_column()?; + state.add_new_task(String::from("T3"), String::from("D3"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + assert_eq!(state.get_selected_column().name, "Done"); + for _ in 0..6 { + state.select_next_column()?; + } + for _ in 0..4 { + state.select_previous_column()?; + } + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_next_column()?; + state.select_next_column()?; + + + // Reload the data from the database then rerun the asserts to + // make sure everything was saved correctly + let mut state = State::new(state.db_conn.0)?; + + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.select_next_task()?; + state.select_next_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.select_previous_column()?; + state.select_previous_column()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_next_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + + Ok(()) + } + + #[test] + fn it_selects_the_correct_first_and_last_task() -> Result<(), Error> { + let mut state = State::new(create_connection()?)?; + + for i in 1..11 { + state.add_new_task(format!("T{i}"), format!("D{i}"))?; + } + assert_eq!(state.get_selected_task().unwrap().title, "T10"); + state.select_last_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T10"); + state.select_first_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + for _ in 0..10 { + state.move_task_down()?; + } + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_first_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.select_last_task()?; + state.select_previous_task()?; + state.select_previous_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T9"); + for _ in 0..10 { + state.move_task_up()?; + } + state.select_first_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T9"); + state.select_last_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + + // Reload the data from the database then rerun the asserts to + // make sure everything was saved correctly + let mut state = State::new(state.db_conn.0)?; + + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_last_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_first_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T9"); + + + Ok(()) + } + + #[test] + fn it_moves_tasks_up_and_down_and_to_different_columns() -> Result<(), Error> { + let mut state = State::new(create_connection()?)?; + + state.add_new_task(String::from("T1"), String::from("D1"))?; + state.add_new_task(String::from("T2"), String::from("D2"))?; + state.select_previous_task()?; + state.move_task_up()?; + state.move_task_down()?; + state.move_task_down()?; + assert_eq!(&state.columns[0].tasks[1].title, "T1"); + assert_eq!(&state.columns[0].tasks[0].title, "T2"); + state.select_next_column()?; + state.add_new_task(String::from("T3"), String::from("D3"))?; + state.move_task_next_column()?; + assert_eq!(state.columns[1].tasks.len(), 0); + assert_eq!(state.columns[2].tasks.len(), 1); + for _ in 0..5 { + state.move_task_next_column()?; + } + for _ in 0..4 { + state.move_task_previous_column()?; + } + assert_eq!(state.columns[0].tasks.len(), 3); + assert_eq!(state.columns[1].tasks.len(), 0); + assert_eq!(state.columns[2].tasks.len(), 0); + assert_eq!(state.columns[3].tasks.len(), 0); + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.select_next_task()?; + state.select_previous_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_previous_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.select_first_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.select_last_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.select_previous_task()?; + + // Reload the data from the database then rerun the asserts to + // make sure everything was saved correctly + let mut state = State::new(state.db_conn.0)?; + + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.select_next_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_next_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.select_first_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.select_last_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + assert_eq!(state.columns[0].tasks.len(), 3); + assert_eq!(state.columns[1].tasks.len(), 0); + assert_eq!(state.columns[2].tasks.len(), 0); + assert_eq!(state.columns[3].tasks.len(), 0); + + Ok(()) + } + + #[test] + fn it_edits_a_task_and_updates_it() -> Result<(), Error> { + let mut state = State::new(create_connection()?)?; + + state.add_new_task(String::from("T1"), String::from("D1"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + assert_eq!(state.get_selected_task().unwrap().description, "D1"); + state.edit_task(String::from("T1"), String::from("D2"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + assert_eq!(state.get_selected_task().unwrap().description, "D2"); + state.edit_task(String::from("T2"), String::from("D1"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + assert_eq!(state.get_selected_task().unwrap().description, "D1"); + for _ in 0..4 { + state.move_task_next_column()?; + } + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + assert_eq!(state.get_selected_task().unwrap().description, "D1"); + state.edit_task(String::from("T3"), String::from("D3"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + assert_eq!(state.get_selected_task().unwrap().description, "D3"); + for _ in 0..4 { + state.move_task_previous_column()?; + } + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + assert_eq!(state.get_selected_task().unwrap().description, "D3"); + state.edit_task(String::from("T3"), String::from("D3"))?; + + assert_eq!(state.columns[0].tasks.len(), 1); + assert_eq!(state.columns[1].tasks.len(), 0); + assert_eq!(state.columns[2].tasks.len(), 0); + assert_eq!(state.columns[3].tasks.len(), 0); + + // Reload the data from the database then rerun the asserts to + // make sure everything was saved correctly + let state = State::new(state.db_conn.0)?; + + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + assert_eq!(state.get_selected_task().unwrap().description, "D1"); + + Ok(()) + } + + #[test] + fn it_deletes_a_task() -> Result<(), Error> { + let mut state = State::new(create_connection()?)?; + + state.add_new_task(String::from("T1"), String::from("D1"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + for col in state.columns.iter().skip(1) { + assert_eq!(col.tasks.len(), 0); + } + state.delete_task()?; + assert!(state.get_selected_task().is_none()); + for col in &state.columns { + assert_eq!(col.tasks.len(), 0); + } + state.delete_task()?; + + state.add_new_task(String::from("T1"), String::from("D1"))?; + state.add_new_task(String::from("T2"), String::from("D2"))?; + state.add_new_task(String::from("T3"), String::from("D3"))?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.delete_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.add_new_task(String::from("T3"), String::from("D3"))?; + state.select_previous_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T2"); + state.delete_task()?; + state.select_previous_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_last_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + state.add_new_task(String::from("T2"), String::from("D2"))?; + for _ in 0..4 { + state.move_task_up()?; + } + state.delete_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T1"); + state.select_next_task()?; + assert_eq!(state.get_selected_task().unwrap().title, "T3"); + for _ in 0..4 { + state.delete_task()?; + } + assert!(state.get_selected_task().is_none()); + for col in &state.columns { + assert_eq!(col.tasks.len(), 0); + } + + // Reload the data from the database then rerun the asserts to + // make sure everything was saved correctly + let state = State::new(state.db_conn.0)?; + + assert!(state.get_selected_task().is_none()); + for col in state.columns { + assert_eq!(col.tasks.len(), 0); + } + + Ok(()) + } +}