diff --git a/src/ui/readarr_ui/library/delete_author_ui.rs b/src/ui/readarr_ui/library/delete_author_ui.rs new file mode 100644 index 00000000..791ce49d --- /dev/null +++ b/src/ui/readarr_ui/library/delete_author_ui.rs @@ -0,0 +1,85 @@ +use ratatui::Frame; +use ratatui::layout::Rect; + +use crate::app::App; +use crate::models::Route; +use crate::models::servarr_data::readarr::readarr_data::{ + ActiveReadarrBlock, DELETE_AUTHOR_BLOCKS, +}; +use crate::ui::DrawUi; +use crate::ui::widgets::checkbox::Checkbox; +use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt; +use crate::ui::widgets::popup::{Popup, Size}; + +#[cfg(test)] +#[path = "delete_author_ui_tests.rs"] +mod delete_author_ui_tests; + +pub(super) struct DeleteAuthorUi; + +impl DrawUi for DeleteAuthorUi { + fn accepts(route: Route) -> bool { + let Route::Readarr(active_readarr_block, _) = route else { + return false; + }; + DELETE_AUTHOR_BLOCKS.contains(&active_readarr_block) + } + + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) { + if matches!( + app.get_current_route(), + Route::Readarr(ActiveReadarrBlock::DeleteAuthorPrompt, _) + ) { + let selected_block = app.data.readarr_data.selected_block.get_active_block(); + let highlights = delete_author_prompt_highlights(selected_block); + let prompt = format!( + "Do you really want to delete the author: \n{}?", + app + .data + .readarr_data + .authors + .current_selection() + .author_name + .text + ); + let checkboxes = vec![ + Checkbox::new("Delete Author Files") + .checked(app.data.readarr_data.delete_files) + .highlighted(highlights.delete_files), + Checkbox::new("Add List Exclusion") + .checked(app.data.readarr_data.add_import_list_exclusion) + .highlighted(highlights.add_import_list_exclusion), + ]; + let confirmation_prompt = ConfirmationPrompt::new() + .title("Delete Author") + .prompt(&prompt) + .checkboxes(checkboxes) + .yes_no_highlighted(highlights.confirm) + .yes_no_value(app.data.readarr_data.prompt_confirm); + + f.render_widget( + Popup::new(confirmation_prompt).size(Size::MediumPrompt), + f.area(), + ); + } + } +} + +#[derive(PartialEq, Eq)] +#[cfg_attr(test, derive(Debug))] +struct DeleteAuthorPromptHighlights { + delete_files: bool, + add_import_list_exclusion: bool, + confirm: bool, +} + +fn delete_author_prompt_highlights( + selected_block: ActiveReadarrBlock, +) -> DeleteAuthorPromptHighlights { + DeleteAuthorPromptHighlights { + delete_files: selected_block == ActiveReadarrBlock::DeleteAuthorToggleDeleteFile, + add_import_list_exclusion: selected_block + == ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion, + confirm: selected_block == ActiveReadarrBlock::DeleteAuthorConfirmPrompt, + } +} diff --git a/src/ui/readarr_ui/library/delete_author_ui_tests.rs b/src/ui/readarr_ui/library/delete_author_ui_tests.rs new file mode 100644 index 00000000..dbcb682e --- /dev/null +++ b/src/ui/readarr_ui/library/delete_author_ui_tests.rs @@ -0,0 +1,155 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::models::servarr_data::readarr::readarr_data::{ + ActiveReadarrBlock, DELETE_AUTHOR_BLOCKS, DELETE_AUTHOR_SELECTION_BLOCKS, + }; + use crate::ui::DrawUi; + use crate::ui::readarr_ui::library::delete_author_ui::{ + DeleteAuthorPromptHighlights, DeleteAuthorUi, delete_author_prompt_highlights, + }; + + #[test] + fn test_delete_author_ui_accepts() { + ActiveReadarrBlock::iter().for_each(|active_readarr_block| { + if DELETE_AUTHOR_BLOCKS.contains(&active_readarr_block) { + assert!(DeleteAuthorUi::accepts(active_readarr_block.into())); + } else { + assert!(!DeleteAuthorUi::accepts(active_readarr_block.into())); + } + }); + } + + #[rstest] + #[case( + ActiveReadarrBlock::DeleteAuthorToggleDeleteFile, + DeleteAuthorPromptHighlights { + delete_files: true, + add_import_list_exclusion: false, + confirm: false, + } + )] + #[case( + ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion, + DeleteAuthorPromptHighlights { + delete_files: false, + add_import_list_exclusion: true, + confirm: false, + } + )] + #[case( + ActiveReadarrBlock::DeleteAuthorConfirmPrompt, + DeleteAuthorPromptHighlights { + delete_files: false, + add_import_list_exclusion: false, + confirm: true, + } + )] + #[case( + ActiveReadarrBlock::Authors, + DeleteAuthorPromptHighlights { + delete_files: false, + add_import_list_exclusion: false, + confirm: false, + } + )] + fn test_delete_author_prompt_highlights( + #[case] selected_block: ActiveReadarrBlock, + #[case] expected_highlights: DeleteAuthorPromptHighlights, + ) { + let highlights = delete_author_prompt_highlights(selected_block); + + assert_eq!(highlights, expected_highlights); + } + + #[test] + fn test_delete_author_prompt_highlights_covers_every_selection_step() { + for step in DELETE_AUTHOR_SELECTION_BLOCKS { + let highlights = delete_author_prompt_highlights(step[0]); + + assert_ne!( + highlights, + delete_author_prompt_highlights(ActiveReadarrBlock::Authors) + ); + } + } + + mod snapshot_tests { + use rstest::rstest; + + use super::*; + use crate::app::App; + use crate::models::BlockSelectionState; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app}; + + fn seed_distinct_delete_preferences(app: &mut App<'_>) { + app.data.readarr_data.delete_files = true; + app.data.readarr_data.add_import_list_exclusion = false; + } + + #[rstest] + #[case(0, ActiveReadarrBlock::DeleteAuthorToggleDeleteFile)] + #[case(1, ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion)] + #[case(2, ActiveReadarrBlock::DeleteAuthorConfirmPrompt)] + fn test_delete_author_ui_renders_step( + #[case] step: usize, + #[case] selected_readarr_block: ActiveReadarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveReadarrBlock::DeleteAuthorPrompt.into()); + seed_distinct_delete_preferences(&mut app); + let mut selected_block = BlockSelectionState::new(DELETE_AUTHOR_SELECTION_BLOCKS); + selected_block.set_index(0, step); + app.data.readarr_data.selected_block = selected_block; + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + DeleteAuthorUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!( + format!("delete_author_step_{step}_{selected_readarr_block}"), + output + ); + } + + #[test] + fn test_delete_author_ui_renders_inverted_delete_preferences() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveReadarrBlock::DeleteAuthorPrompt.into()); + app.data.readarr_data.delete_files = false; + app.data.readarr_data.add_import_list_exclusion = true; + app.data.readarr_data.selected_block = + BlockSelectionState::new(DELETE_AUTHOR_SELECTION_BLOCKS); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + DeleteAuthorUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } + + #[rstest] + #[case(ActiveReadarrBlock::DeleteAuthorConfirmPrompt)] + #[case(ActiveReadarrBlock::DeleteAuthorToggleDeleteFile)] + #[case(ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion)] + fn test_delete_author_ui_renders_nothing_when_route_is_not_the_prompt( + #[case] active_readarr_block: ActiveReadarrBlock, + ) { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(active_readarr_block.into()); + seed_distinct_delete_preferences(&mut app); + app.data.readarr_data.selected_block = + BlockSelectionState::new(DELETE_AUTHOR_SELECTION_BLOCKS); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + DeleteAuthorUi::draw(f, app, f.area()); + }); + + assert!(!output.contains("Delete Author")); + assert!(!output.contains("Add List Exclusion")); + } + } +} diff --git a/src/ui/readarr_ui/library/library_ui_tests.rs b/src/ui/readarr_ui/library/library_ui_tests.rs index 8432d7a0..2e56081a 100644 --- a/src/ui/readarr_ui/library/library_ui_tests.rs +++ b/src/ui/readarr_ui/library/library_ui_tests.rs @@ -5,7 +5,7 @@ mod tests { use crate::models::readarr_models::{Author, AuthorStatistics, AuthorStatus}; use crate::models::servarr_data::readarr::readarr_data::{ ADD_AUTHOR_BLOCKS, AUTHOR_DETAILS_BLOCKS, ActiveReadarrBlock, BOOK_DETAILS_BLOCKS, - EDIT_AUTHOR_BLOCKS, EDITION_DETAILS_BLOCKS, LIBRARY_BLOCKS, + DELETE_AUTHOR_BLOCKS, EDIT_AUTHOR_BLOCKS, EDITION_DETAILS_BLOCKS, LIBRARY_BLOCKS, }; use crate::ui::DrawUi; use crate::ui::readarr_ui::library::{LibraryUi, decorate_author_row_with_style}; @@ -20,6 +20,7 @@ mod tests { blocks.extend(BOOK_DETAILS_BLOCKS); blocks.extend(EDITION_DETAILS_BLOCKS); blocks.extend(ADD_AUTHOR_BLOCKS); + blocks.extend(DELETE_AUTHOR_BLOCKS); blocks.extend(EDIT_AUTHOR_BLOCKS); for active_readarr_block in ActiveReadarrBlock::iter() { @@ -190,7 +191,7 @@ mod tests { use crate::app::App; use crate::models::BlockSelectionState; use crate::models::servarr_data::readarr::readarr_data::{ - ActiveReadarrBlock, EDIT_AUTHOR_SELECTION_BLOCKS, + ActiveReadarrBlock, DELETE_AUTHOR_SELECTION_BLOCKS, EDIT_AUTHOR_SELECTION_BLOCKS, }; use rstest::rstest; @@ -297,5 +298,22 @@ mod tests { insta::assert_snapshot!(output); } + + #[test] + fn test_library_ui_renders_delete_author_over_library() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveReadarrBlock::Authors.into()); + app.data.readarr_data.delete_files = true; + app.data.readarr_data.add_import_list_exclusion = false; + app.data.readarr_data.selected_block = + BlockSelectionState::new(DELETE_AUTHOR_SELECTION_BLOCKS); + app.push_navigation_stack(ActiveReadarrBlock::DeleteAuthorPrompt.into()); + + let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| { + LibraryUi::draw(f, app, f.area()); + }); + + insta::assert_snapshot!(output); + } } } diff --git a/src/ui/readarr_ui/library/mod.rs b/src/ui/readarr_ui/library/mod.rs index 89a7e6e2..c1883812 100644 --- a/src/ui/readarr_ui/library/mod.rs +++ b/src/ui/readarr_ui/library/mod.rs @@ -1,5 +1,6 @@ use add_author_ui::AddAuthorUi; use author_details_ui::AuthorDetailsUi; +use delete_author_ui::DeleteAuthorUi; use edit_author_ui::EditAuthorUi; use ratatui::{ Frame, @@ -30,6 +31,7 @@ use crate::{ mod add_author_ui; mod author_details_ui; mod book_details_ui; +mod delete_author_ui; mod edit_author_ui; mod edition_details_ui; @@ -43,6 +45,7 @@ impl DrawUi for LibraryUi { fn accepts(route: Route) -> bool { if let Route::Readarr(active_readarr_block, _) = route { return AddAuthorUi::accepts(route) + || DeleteAuthorUi::accepts(route) || EditAuthorUi::accepts(route) || AuthorDetailsUi::accepts(route) || LIBRARY_BLOCKS.contains(&active_readarr_block); @@ -57,6 +60,7 @@ impl DrawUi for LibraryUi { match route { _ if AddAuthorUi::accepts(route) => AddAuthorUi::draw(f, app, area), + _ if DeleteAuthorUi::accepts(route) => DeleteAuthorUi::draw(f, app, area), _ if AuthorDetailsUi::accepts(route) => AuthorDetailsUi::draw(f, app, area), _ if EditAuthorUi::accepts(route) => EditAuthorUi::draw(f, app, area), Route::Readarr(ActiveReadarrBlock::UpdateAllAuthorsPrompt, _) => { diff --git a/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_0_DeleteAuthorToggleDeleteFile.snap b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_0_DeleteAuthorToggleDeleteFile.snap new file mode 100644 index 00000000..7847edec --- /dev/null +++ b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_0_DeleteAuthorToggleDeleteFile.snap @@ -0,0 +1,38 @@ +--- +source: src/ui/readarr_ui/library/delete_author_ui_tests.rs +expression: output +--- + + + + + + + + + + + + + + + + + ╭───────────────────── Delete Author ─────────────────────╮ + │ Do you really want to delete the author: │ + │ Test Author? │ + │ │ + │ │ + │ ╭───╮ │ + │ Delete Author Files: │ ✔ │ │ + │ ╰───╯ │ + │ ╭───╮ │ + │ Add List Exclusion: │ │ │ + │ ╰───╯ │ + │ │ + │ │ + │ │ + │╭────────────────────────────╮╭───────────────────────────╮│ + ││ Yes ││ No ││ + │╰────────────────────────────╯╰───────────────────────────╯│ + ╰───────────────────────────────────────────────────────────╯ diff --git a/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_1_DeleteAuthorToggleAddListExclusion.snap b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_1_DeleteAuthorToggleAddListExclusion.snap new file mode 100644 index 00000000..7847edec --- /dev/null +++ b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_1_DeleteAuthorToggleAddListExclusion.snap @@ -0,0 +1,38 @@ +--- +source: src/ui/readarr_ui/library/delete_author_ui_tests.rs +expression: output +--- + + + + + + + + + + + + + + + + + ╭───────────────────── Delete Author ─────────────────────╮ + │ Do you really want to delete the author: │ + │ Test Author? │ + │ │ + │ │ + │ ╭───╮ │ + │ Delete Author Files: │ ✔ │ │ + │ ╰───╯ │ + │ ╭───╮ │ + │ Add List Exclusion: │ │ │ + │ ╰───╯ │ + │ │ + │ │ + │ │ + │╭────────────────────────────╮╭───────────────────────────╮│ + ││ Yes ││ No ││ + │╰────────────────────────────╯╰───────────────────────────╯│ + ╰───────────────────────────────────────────────────────────╯ diff --git a/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_2_DeleteAuthorConfirmPrompt.snap b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_2_DeleteAuthorConfirmPrompt.snap new file mode 100644 index 00000000..7847edec --- /dev/null +++ b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_step_2_DeleteAuthorConfirmPrompt.snap @@ -0,0 +1,38 @@ +--- +source: src/ui/readarr_ui/library/delete_author_ui_tests.rs +expression: output +--- + + + + + + + + + + + + + + + + + ╭───────────────────── Delete Author ─────────────────────╮ + │ Do you really want to delete the author: │ + │ Test Author? │ + │ │ + │ │ + │ ╭───╮ │ + │ Delete Author Files: │ ✔ │ │ + │ ╰───╯ │ + │ ╭───╮ │ + │ Add List Exclusion: │ │ │ + │ ╰───╯ │ + │ │ + │ │ + │ │ + │╭────────────────────────────╮╭───────────────────────────╮│ + ││ Yes ││ No ││ + │╰────────────────────────────╯╰───────────────────────────╯│ + ╰───────────────────────────────────────────────────────────╯ diff --git a/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_ui_renders_inverted_delete_preferences.snap b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_ui_renders_inverted_delete_preferences.snap new file mode 100644 index 00000000..028d0ebe --- /dev/null +++ b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__delete_author_ui__delete_author_ui_tests__tests__snapshot_tests__delete_author_ui_renders_inverted_delete_preferences.snap @@ -0,0 +1,38 @@ +--- +source: src/ui/readarr_ui/library/delete_author_ui_tests.rs +expression: output +--- + + + + + + + + + + + + + + + + + ╭───────────────────── Delete Author ─────────────────────╮ + │ Do you really want to delete the author: │ + │ Test Author? │ + │ │ + │ │ + │ ╭───╮ │ + │ Delete Author Files: │ │ │ + │ ╰───╯ │ + │ ╭───╮ │ + │ Add List Exclusion: │ ✔ │ │ + │ ╰───╯ │ + │ │ + │ │ + │ │ + │╭────────────────────────────╮╭───────────────────────────╮│ + ││ Yes ││ No ││ + │╰────────────────────────────╯╰───────────────────────────╯│ + ╰───────────────────────────────────────────────────────────╯ diff --git a/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_author_over_library.snap b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_author_over_library.snap new file mode 100644 index 00000000..31bd8c98 --- /dev/null +++ b/src/ui/readarr_ui/library/snapshots/managarr__ui__readarr_ui__library__library_ui_tests__tests__snapshot_tests__library_ui_renders_delete_author_over_library.snap @@ -0,0 +1,38 @@ +--- +source: src/ui/readarr_ui/library/library_ui_tests.rs +expression: output +--- +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Name ▼ Status Quality Profile Metadata Profile Books Size Monitored Tags +=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex + + + + + + + + + + + + + + ╭───────────────────── Delete Author ─────────────────────╮ + │ Do you really want to delete the author: │ + │ Test Author? │ + │ │ + │ │ + │ ╭───╮ │ + │ Delete Author Files: │ ✔ │ │ + │ ╰───╯ │ + │ ╭───╮ │ + │ Add List Exclusion: │ │ │ + │ ╰───╯ │ + │ │ + │ │ + │ │ + │╭────────────────────────────╮╭───────────────────────────╮│ + ││ Yes ││ No ││ + │╰────────────────────────────╯╰───────────────────────────╯│ + ╰───────────────────────────────────────────────────────────╯