Refactored the UI module and the handlers module to do a more chain-of-responsibility method to manage the UI's and handlers for different key events. Also, initial work for indexer settings as well

This commit is contained in:
2023-08-08 10:50:07 -06:00
parent 718613d59f
commit cf11527fef
67 changed files with 5255 additions and 2216 deletions
+5
View File
@@ -22,6 +22,7 @@ generate_keybindings! {
edit,
logs,
tasks,
restrictions,
refresh,
update,
events,
@@ -95,6 +96,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
key: Key::Char('t'),
desc: "Tasks",
},
restrictions: KeyBinding {
key: Key::Char('t'),
desc: "Restrictions",
},
refresh: KeyBinding {
key: Key::Char('r'),
desc: "Refresh",
+187 -133
View File
@@ -1,6 +1,7 @@
use bimap::BiMap;
use chrono::{DateTime, Utc};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use crate::app::{App, Route};
use crate::models::radarr_models::{
@@ -250,134 +251,134 @@ impl<'a> RadarrData<'a> {
impl<'a> Default for RadarrData<'a> {
fn default() -> RadarrData<'a> {
RadarrData {
root_folders: StatefulTable::default(),
disk_space_vec: Vec::new(),
version: String::default(),
start_time: DateTime::default(),
movies: StatefulTable::default(),
add_searched_movies: StatefulTable::default(),
monitor_list: StatefulList::default(),
minimum_availability_list: StatefulList::default(),
quality_profile_list: StatefulList::default(),
root_folder_list: StatefulList::default(),
selected_block: BlockSelectionState::default(),
filtered_movies: StatefulTable::default(),
downloads: StatefulTable::default(),
indexers: StatefulTable::default(),
indexer_settings: None,
quality_profile_map: BiMap::default(),
tags_map: BiMap::default(),
file_details: String::default(),
audio_details: String::default(),
video_details: String::default(),
movie_details: ScrollableText::default(),
movie_history: StatefulTable::default(),
movie_cast: StatefulTable::default(),
movie_crew: StatefulTable::default(),
movie_releases: StatefulTable::default(),
movie_releases_sort: StatefulList::default(),
collections: StatefulTable::default(),
filtered_collections: StatefulTable::default(),
collection_movies: StatefulTable::default(),
logs: StatefulList::default(),
log_details: StatefulList::default(),
tasks: StatefulTable::default(),
queued_events: StatefulTable::default(),
updates: ScrollableText::default(),
prompt_confirm_action: None,
search: HorizontallyScrollableText::default(),
filter: HorizontallyScrollableText::default(),
edit_path: HorizontallyScrollableText::default(),
edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None,
edit_search_on_add: None,
sort_ascending: None,
is_searching: false,
is_filtering: false,
prompt_confirm: false,
delete_movie_files: false,
add_list_exclusion: false,
main_tabs: TabState::new(vec![
TabRoute {
title: "Library",
route: ActiveRadarrBlock::Movies.into(),
help: "",
contextual_help: Some("<a> add | <e> edit | <del> delete | <s> search | <f> filter | <r> refresh | <u> update all | <enter> details | <esc> cancel filter"),
},
TabRoute {
title: "Downloads",
route: ActiveRadarrBlock::Downloads.into(),
help: "",
contextual_help: Some("<r> refresh | <del> delete"),
},
TabRoute {
title: "Collections",
route: ActiveRadarrBlock::Collections.into(),
help: "",
contextual_help: Some("<s> search | <e> edit | <f> filter | <r> refresh | <u> update all | <enter> details | <esc> cancel filter"),
},
TabRoute {
title: "Root Folders",
route: ActiveRadarrBlock::RootFolders.into(),
help: "",
contextual_help: Some("<a> add | <del> delete | <r> refresh"),
},
TabRoute {
title: "Indexers",
route: ActiveRadarrBlock::Indexers.into(),
help: "",
contextual_help: Some("<enter> edit | <s> settings | <del> delete | <r> refresh"),
},
TabRoute {
title: "System",
route: ActiveRadarrBlock::System.into(),
help: "",
contextual_help: Some("<t> open tasks | <e> open events | <l> open logs | <u> open updates | <r> refresh")
}
]),
movie_info_tabs: TabState::new(vec![
TabRoute {
title: "Details",
route: ActiveRadarrBlock::MovieDetails.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None
},
TabRoute {
title: "History",
route: ActiveRadarrBlock::MovieHistory.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None
},
TabRoute {
title: "File",
route: ActiveRadarrBlock::FileInfo.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "Cast",
route: ActiveRadarrBlock::Cast.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "Crew",
route: ActiveRadarrBlock::Crew.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "Manual Search",
route: ActiveRadarrBlock::ManualSearch.into(),
help: "<r> refresh | <u> update | <e> edit | <o> sort | <s> auto search | <esc> close",
contextual_help: Some("<enter> details")
}
]),
}
root_folders: StatefulTable::default(),
disk_space_vec: Vec::new(),
version: String::default(),
start_time: DateTime::default(),
movies: StatefulTable::default(),
add_searched_movies: StatefulTable::default(),
monitor_list: StatefulList::default(),
minimum_availability_list: StatefulList::default(),
quality_profile_list: StatefulList::default(),
root_folder_list: StatefulList::default(),
selected_block: BlockSelectionState::default(),
filtered_movies: StatefulTable::default(),
downloads: StatefulTable::default(),
indexers: StatefulTable::default(),
indexer_settings: None,
quality_profile_map: BiMap::default(),
tags_map: BiMap::default(),
file_details: String::default(),
audio_details: String::default(),
video_details: String::default(),
movie_details: ScrollableText::default(),
movie_history: StatefulTable::default(),
movie_cast: StatefulTable::default(),
movie_crew: StatefulTable::default(),
movie_releases: StatefulTable::default(),
movie_releases_sort: StatefulList::default(),
collections: StatefulTable::default(),
filtered_collections: StatefulTable::default(),
collection_movies: StatefulTable::default(),
logs: StatefulList::default(),
log_details: StatefulList::default(),
tasks: StatefulTable::default(),
queued_events: StatefulTable::default(),
updates: ScrollableText::default(),
prompt_confirm_action: None,
search: HorizontallyScrollableText::default(),
filter: HorizontallyScrollableText::default(),
edit_path: HorizontallyScrollableText::default(),
edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None,
edit_search_on_add: None,
sort_ascending: None,
is_searching: false,
is_filtering: false,
prompt_confirm: false,
delete_movie_files: false,
add_list_exclusion: false,
main_tabs: TabState::new(vec![
TabRoute {
title: "Library",
route: ActiveRadarrBlock::Movies.into(),
help: "",
contextual_help: Some("<a> add | <e> edit | <del> delete | <s> search | <f> filter | <r> refresh | <u> update all | <enter> details | <esc> cancel filter"),
},
TabRoute {
title: "Downloads",
route: ActiveRadarrBlock::Downloads.into(),
help: "",
contextual_help: Some("<r> refresh | <del> delete"),
},
TabRoute {
title: "Collections",
route: ActiveRadarrBlock::Collections.into(),
help: "",
contextual_help: Some("<s> search | <e> edit | <f> filter | <r> refresh | <u> update all | <enter> details | <esc> cancel filter"),
},
TabRoute {
title: "Root Folders",
route: ActiveRadarrBlock::RootFolders.into(),
help: "",
contextual_help: Some("<a> add | <del> delete | <r> refresh"),
},
TabRoute {
title: "Indexers",
route: ActiveRadarrBlock::Indexers.into(),
help: "",
contextual_help: Some("<a> add | <enter> edit | <s> settings | <t> restrictions | <del> delete | <r> refresh"),
},
TabRoute {
title: "System",
route: ActiveRadarrBlock::System.into(),
help: "",
contextual_help: Some("<t> open tasks | <e> open events | <l> open logs | <u> open updates | <r> refresh"),
},
]),
movie_info_tabs: TabState::new(vec![
TabRoute {
title: "Details",
route: ActiveRadarrBlock::MovieDetails.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "History",
route: ActiveRadarrBlock::MovieHistory.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "File",
route: ActiveRadarrBlock::FileInfo.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "Cast",
route: ActiveRadarrBlock::Cast.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "Crew",
route: ActiveRadarrBlock::Crew.into(),
help: "<r> refresh | <u> update | <e> edit | <s> auto search | <esc> close",
contextual_help: None,
},
TabRoute {
title: "Manual Search",
route: ActiveRadarrBlock::ManualSearch.into(),
help: "<r> refresh | <u> update | <e> edit | <o> sort | <s> auto search | <esc> close",
contextual_help: Some("<enter> details"),
},
]),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, EnumIter)]
pub enum ActiveRadarrBlock {
AddIndexer,
AddMovieAlreadyInLibrary,
@@ -424,7 +425,16 @@ pub enum ActiveRadarrBlock {
FilterCollections,
FilterMovies,
Indexers,
IndexerSettings,
IndexerSettingsPrompt,
IndexerSettingsAvailabilityDelayInput,
IndexerSettingsConfirmPrompt,
IndexerSettingsMaximumSizeInput,
IndexerSettingsMinimumAgeInput,
IndexerSettingsRetentionInput,
IndexerSettingsRssSyncIntervalInput,
IndexerSettingsToggleAllowHardcodedSubs,
IndexerSettingsTogglePreferIndexerFlags,
IndexerSettingsWhitelistedSubtitleTagsInput,
ManualSearch,
ManualSearchSortPrompt,
ManualSearchConfirmPrompt,
@@ -448,6 +458,29 @@ pub enum ActiveRadarrBlock {
ViewMovieOverview,
}
pub static LIBRARY_BLOCKS: [ActiveRadarrBlock; 4] = [
ActiveRadarrBlock::Movies,
ActiveRadarrBlock::SearchMovie,
ActiveRadarrBlock::FilterMovies,
ActiveRadarrBlock::UpdateAllMoviesPrompt,
];
pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 4] = [
ActiveRadarrBlock::Collections,
ActiveRadarrBlock::SearchCollection,
ActiveRadarrBlock::FilterCollections,
ActiveRadarrBlock::UpdateAllCollectionsPrompt,
];
pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 4] = [
ActiveRadarrBlock::AddIndexer,
ActiveRadarrBlock::EditIndexer,
ActiveRadarrBlock::DeleteIndexerPrompt,
ActiveRadarrBlock::Indexers,
];
pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [
ActiveRadarrBlock::RootFolders,
ActiveRadarrBlock::AddRootFolderPrompt,
ActiveRadarrBlock::DeleteRootFolderPrompt,
];
pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [
ActiveRadarrBlock::AddMovieSearchInput,
ActiveRadarrBlock::AddMovieSearchResults,
@@ -502,6 +535,11 @@ pub static EDIT_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [
ActiveRadarrBlock::EditMovieTagsInput,
ActiveRadarrBlock::EditMovieConfirmPrompt,
];
pub static DOWNLOADS_BLOCKS: [ActiveRadarrBlock; 3] = [
ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt,
ActiveRadarrBlock::UpdateDownloadsPrompt,
];
pub static MOVIE_DETAILS_BLOCKS: [ActiveRadarrBlock; 10] = [
ActiveRadarrBlock::MovieDetails,
ActiveRadarrBlock::MovieHistory,
@@ -537,12 +575,28 @@ pub static DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [
ActiveRadarrBlock::DeleteMovieToggleAddListExclusion,
ActiveRadarrBlock::DeleteMovieConfirmPrompt,
];
pub static INDEXER_BLOCKS: [ActiveRadarrBlock; 5] = [
ActiveRadarrBlock::Indexers,
ActiveRadarrBlock::IndexerSettings,
ActiveRadarrBlock::AddIndexer,
ActiveRadarrBlock::EditIndexer,
ActiveRadarrBlock::DeleteIndexerPrompt,
pub static INDEXER_SETTINGS_BLOCKS: [ActiveRadarrBlock; 10] = [
ActiveRadarrBlock::IndexerSettingsPrompt,
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
ActiveRadarrBlock::IndexerSettingsConfirmPrompt,
ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
ActiveRadarrBlock::IndexerSettingsRetentionInput,
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags,
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
];
pub static INDEXER_SETTINGS_SELECTION_BLOCKS: [ActiveRadarrBlock; 9] = [
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
ActiveRadarrBlock::IndexerSettingsRetentionInput,
ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags,
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
ActiveRadarrBlock::IndexerSettingsConfirmPrompt,
];
pub static SYSTEM_DETAILS_BLOCKS: [ActiveRadarrBlock; 5] = [
ActiveRadarrBlock::SystemLogs,
@@ -600,7 +654,7 @@ impl<'a> App<'a> {
.dispatch_network_event(RadarrEvent::GetIndexers.into())
.await;
}
ActiveRadarrBlock::IndexerSettings => {
ActiveRadarrBlock::IndexerSettingsPrompt => {
self
.dispatch_network_event(RadarrEvent::GetIndexerSettings.into())
.await;
+49 -3
View File
@@ -30,7 +30,7 @@ mod tests {
)),
Route::Radarr(
ActiveRadarrBlock::AddMoviePrompt,
Some(ActiveRadarrBlock::AddMovieSearchResults)
Some(ActiveRadarrBlock::AddMovieSearchResults),
)
);
}
@@ -358,7 +358,9 @@ mod tests {
assert!(radarr_data.main_tabs.tabs[4].help.is_empty());
assert_eq!(
radarr_data.main_tabs.tabs[4].contextual_help,
Some("<enter> edit | <s> settings | <del> delete | <r> refresh")
Some(
"<a> add | <enter> edit | <s> settings | <t> restrictions | <del> delete | <r> refresh"
)
);
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "System");
@@ -461,6 +463,7 @@ mod tests {
use crate::app::radarr::{
ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS,
EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS,
INDEXER_SETTINGS_SELECTION_BLOCKS,
};
#[test]
@@ -556,6 +559,7 @@ mod tests {
#[test]
fn test_delete_movie_prompt_block_order() {
let mut delete_movie_block_iter = DELETE_MOVIE_SELECTION_BLOCKS.iter();
assert_eq!(
delete_movie_block_iter.next().unwrap(),
&ActiveRadarrBlock::DeleteMovieToggleDeleteFile
@@ -569,6 +573,48 @@ mod tests {
&ActiveRadarrBlock::DeleteMovieConfirmPrompt
);
}
#[test]
fn test_indexer_settings_prompt_block_order() {
let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter();
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsMinimumAgeInput
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsRetentionInput
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsMaximumSizeInput
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&ActiveRadarrBlock::IndexerSettingsConfirmPrompt
);
}
}
mod radarr_tests {
@@ -719,7 +765,7 @@ mod tests {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_radarr_block(&ActiveRadarrBlock::IndexerSettings)
.dispatch_by_radarr_block(&ActiveRadarrBlock::IndexerSettingsPrompt)
.await;
assert!(app.is_loading);
+2 -2
View File
@@ -445,12 +445,12 @@ mod test_utils {
#[macro_export]
macro_rules! test_handler_delegation {
($base:expr, $active_block:expr) => {
($handler:ident, $base:expr, $active_block:expr) => {
let mut app = App::default();
app.push_navigation_stack($base.clone().into());
app.push_navigation_stack($active_block.clone().into());
RadarrHandler::with(
$handler::with(
&DEFAULT_KEYBINDINGS.esc.key,
&mut app,
&$active_block,
+1
View File
@@ -37,6 +37,7 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route>> {
self.handle_key_event();
}
fn accepts(active_block: &'a T) -> bool;
fn with(key: &'a Key, app: &'a mut App<'b>, active_block: &'a T, context: &'a Option<T>) -> Self;
fn get_key(&self) -> &Key;
fn handle_scroll_up(&mut self);
@@ -1,6 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{
ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, COLLECTION_DETAILS_BLOCKS,
EDIT_COLLECTION_SELECTION_BLOCKS,
};
use crate::app::App;
use crate::event::Key;
@@ -19,6 +20,10 @@ pub(super) struct CollectionDetailsHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
COLLECTION_DETAILS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -1,12 +1,13 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::CollectionMovie;
use crate::models::HorizontallyScrollableText;
@@ -207,7 +208,6 @@ mod tests {
use crate::app::radarr::RadarrData;
use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS;
use crate::models::radarr_models::{Collection, MinimumAvailability};
use crate::models::BlockSelectionState;
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable;
use crate::test_edit_collection_key;
@@ -223,4 +223,15 @@ mod tests {
);
}
}
#[test]
fn test_collection_details_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(CollectionDetailsHandler::accepts(&active_radarr_block));
} else {
assert!(!CollectionDetailsHandler::accepts(&active_radarr_block));
}
});
}
}
@@ -0,0 +1,673 @@
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{
ActiveRadarrBlock, COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS,
};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::Collection;
use crate::models::HorizontallyScrollableText;
use crate::{extended_stateful_iterable_vec, test_handler_delegation};
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_collections_scroll,
CollectionsHandler,
collections,
simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText),
ActiveRadarrBlock::Collections,
None,
title,
to_string
);
test_iterable_scroll!(
test_filtered_collections_scroll,
CollectionsHandler,
filtered_collections,
simple_stateful_iterable_vec!(Collection, HorizontallyScrollableText),
ActiveRadarrBlock::Collections,
None,
title,
to_string
);
}
mod test_handle_home_end {
use pretty_assertions::assert_eq;
use crate::{
extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys,
};
use super::*;
test_iterable_home_and_end!(
test_collections_home_end,
CollectionsHandler,
collections,
extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText),
ActiveRadarrBlock::Collections,
None,
title,
to_string
);
test_iterable_home_and_end!(
test_filtered_collections_home_end,
CollectionsHandler,
filtered_collections,
extended_stateful_iterable_vec!(Collection, HorizontallyScrollableText),
ActiveRadarrBlock::Collections,
None,
title,
to_string
);
#[test]
fn test_collection_search_box_home_end_keys() {
test_text_box_home_end_keys!(
CollectionsHandler,
ActiveRadarrBlock::SearchCollection,
search
);
}
#[test]
fn test_collection_filter_box_home_end_keys() {
test_text_box_home_end_keys!(
CollectionsHandler,
ActiveRadarrBlock::FilterCollections,
filter
);
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::test_text_box_left_right_keys;
use super::*;
#[test]
fn test_collections_tab_left() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(2);
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.left.key,
&mut app,
&ActiveRadarrBlock::Collections,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
}
#[test]
fn test_collections_tab_right() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(2);
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.right.key,
&mut app,
&ActiveRadarrBlock::Collections,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::RootFolders.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
}
#[rstest]
fn test_left_right_update_all_collections_prompt_toggle(
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::default();
CollectionsHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::UpdateAllCollectionsPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
CollectionsHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::UpdateAllCollectionsPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_collection_search_box_left_right_keys() {
test_text_box_left_right_keys!(
CollectionsHandler,
ActiveRadarrBlock::SearchCollection,
search
);
}
#[test]
fn test_collection_filter_box_left_right_keys() {
test_text_box_left_right_keys!(
CollectionsHandler,
ActiveRadarrBlock::FilterCollections,
filter
);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use crate::network::radarr_network::RadarrEvent;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_collections_submit() {
let mut app = App::default();
CollectionsHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::Collections,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::CollectionDetails.into()
);
}
#[test]
fn test_search_collections_submit() {
let mut app = App::default();
app
.data
.radarr_data
.collections
.set_items(extended_stateful_iterable_vec!(
Collection,
HorizontallyScrollableText
));
app.data.radarr_data.search = "Test 2".to_owned().into();
CollectionsHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::SearchCollection,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.collections
.current_selection()
.title
.text,
"Test 2"
);
}
#[test]
fn test_search_filtered_collections_submit() {
let mut app = App::default();
app
.data
.radarr_data
.filtered_collections
.set_items(extended_stateful_iterable_vec!(
Collection,
HorizontallyScrollableText
));
app.data.radarr_data.search = "Test 2".to_owned().into();
CollectionsHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::SearchCollection,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.filtered_collections
.current_selection()
.title
.text,
"Test 2"
);
}
#[test]
fn test_filter_collections_submit() {
let mut app = App::default();
app
.data
.radarr_data
.collections
.set_items(extended_stateful_iterable_vec!(
Collection,
HorizontallyScrollableText
));
app.data.radarr_data.filter = "Test".to_owned().into();
CollectionsHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::FilterCollections,
&None,
)
.handle();
assert_eq!(app.data.radarr_data.filtered_collections.items.len(), 3);
assert_str_eq!(
app
.data
.radarr_data
.filtered_collections
.current_selection()
.title
.text,
"Test 1"
);
}
#[test]
fn test_update_all_collections_prompt_confirm_submit() {
let mut app = App::default();
app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
CollectionsHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::UpdateAllCollectionsPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::UpdateCollections)
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
}
#[test]
fn test_update_all_collections_prompt_decline_submit() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
CollectionsHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::UpdateAllCollectionsPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data;
use crate::{assert_filter_reset, assert_search_reset};
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_search_collection_block_esc() {
let mut app = App::default();
app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
app.data.radarr_data = create_test_radarr_data();
CollectionsHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::SearchCollection,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
assert!(!app.should_ignore_quit_key);
assert_search_reset!(app.data.radarr_data);
}
#[test]
fn test_filter_collections_block_esc() {
let mut app = App::default();
app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
app.data.radarr_data = create_test_radarr_data();
CollectionsHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::FilterCollections,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
assert!(!app.should_ignore_quit_key);
assert_filter_reset!(app.data.radarr_data);
}
#[test]
fn test_update_all_collections_prompt_block_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
app.data.radarr_data.prompt_confirm = true;
CollectionsHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::UpdateAllCollectionsPrompt,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_default_esc() {
let mut app = App::default();
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.data.radarr_data = create_test_radarr_data();
CollectionsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Collections, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
assert!(app.error.text.is_empty());
assert_search_reset!(app.data.radarr_data);
assert_filter_reset!(app.data.radarr_data);
}
}
mod test_handle_key_char {
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::Number;
use strum::IntoEnumIterator;
use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data;
use crate::app::radarr::RadarrData;
use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS;
use crate::models::radarr_models::MinimumAvailability;
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable;
use crate::{assert_refresh_key, test_edit_collection_key};
use super::*;
#[test]
fn test_search_collections_key() {
let mut app = App::default();
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.search.key,
&mut app,
&ActiveRadarrBlock::Collections,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::SearchCollection.into()
);
assert!(app.data.radarr_data.is_searching);
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_filter_collections_key() {
let mut app = App::default();
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.filter.key,
&mut app,
&ActiveRadarrBlock::Collections,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::FilterCollections.into()
);
assert!(app.data.radarr_data.is_filtering);
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_collection_edit_key() {
test_edit_collection_key!(
CollectionsHandler,
ActiveRadarrBlock::Collections,
ActiveRadarrBlock::Collections
);
}
#[test]
fn test_update_key() {
let mut app = App::default();
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.update.key,
&mut app,
&ActiveRadarrBlock::Collections,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()
);
}
#[test]
fn test_refresh_collections_key() {
assert_refresh_key!(CollectionsHandler, ActiveRadarrBlock::Collections);
}
#[test]
fn test_search_collections_box_backspace_key() {
let mut app = App::default();
app.data.radarr_data.search = "Test".to_owned().into();
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key,
&mut app,
&ActiveRadarrBlock::SearchCollection,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.search.text, "Tes");
}
#[test]
fn test_filter_collections_box_backspace_key() {
let mut app = App::default();
app.data.radarr_data.filter = "Test".to_owned().into();
CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key,
&mut app,
&ActiveRadarrBlock::FilterCollections,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.filter.text, "Tes");
}
#[test]
fn test_search_collections_box_char_key() {
let mut app = App::default();
CollectionsHandler::with(
&Key::Char('h'),
&mut app,
&ActiveRadarrBlock::SearchCollection,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.search.text, "h");
}
#[test]
fn test_filter_collections_box_char_key() {
let mut app = App::default();
CollectionsHandler::with(
&Key::Char('h'),
&mut app,
&ActiveRadarrBlock::FilterCollections,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.filter.text, "h");
}
}
#[rstest]
fn test_delegate_collection_details_blocks_to_collection_details_handler(
#[values(
ActiveRadarrBlock::CollectionDetails,
ActiveRadarrBlock::ViewMovieOverview
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
CollectionsHandler,
ActiveRadarrBlock::Collections,
active_radarr_block
);
}
#[rstest]
fn test_delegate_edit_collection_blocks_to_edit_collection_handler(
#[values(
ActiveRadarrBlock::EditCollectionPrompt,
ActiveRadarrBlock::EditCollectionRootFolderPathInput,
ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
ActiveRadarrBlock::EditCollectionSelectQualityProfile
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
CollectionsHandler,
ActiveRadarrBlock::Collections,
active_radarr_block
);
}
#[test]
fn test_collections_handler_accepts() {
let mut collections_handler_blocks = Vec::new();
collections_handler_blocks.extend(COLLECTIONS_BLOCKS);
collections_handler_blocks.extend(COLLECTION_DETAILS_BLOCKS);
collections_handler_blocks.extend(EDIT_COLLECTION_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if collections_handler_blocks.contains(&active_radarr_block) {
assert!(CollectionsHandler::accepts(&active_radarr_block));
} else {
assert!(!CollectionsHandler::accepts(&active_radarr_block));
}
});
}
}
@@ -1,5 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -19,6 +19,10 @@ pub(super) struct EditCollectionHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
EDIT_COLLECTION_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -1,12 +1,13 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::MinimumAvailability;
@@ -500,4 +501,15 @@ mod tests {
assert_str_eq!(app.data.radarr_data.edit_path.text, "h");
}
}
#[test]
fn test_edit_collection_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) {
assert!(EditCollectionHandler::accepts(&active_radarr_block));
} else {
assert!(!EditCollectionHandler::accepts(&active_radarr_block));
}
});
}
}
@@ -0,0 +1,311 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::{
filter_table, handle_change_tab_left_right_keys, search_table,
};
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
mod collection_details_handler;
mod edit_collection_handler;
#[cfg(test)]
#[path = "collections_handler_tests.rs"]
mod collections_handler_tests;
pub(super) struct CollectionsHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> {
fn handle(&mut self) {
match self.active_radarr_block {
_ if CollectionDetailsHandler::accepts(self.active_radarr_block) => {
CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ if EditCollectionHandler::accepts(self.active_radarr_block) => {
EditCollectionHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ => self.handle_key_event(),
}
}
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
CollectionDetailsHandler::accepts(active_block)
|| EditCollectionHandler::accepts(active_block)
|| COLLECTIONS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
) -> CollectionsHandler<'a, 'b> {
CollectionsHandler {
key,
app,
active_radarr_block: active_block,
context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Collections {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self.app.data.radarr_data.filtered_collections.scroll_up();
} else {
self.app.data.radarr_data.collections.scroll_up()
}
}
}
fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Collections {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self.app.data.radarr_data.filtered_collections.scroll_down();
} else {
self.app.data.radarr_data.collections.scroll_down()
}
}
}
fn handle_home(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self
.app
.data
.radarr_data
.filtered_collections
.scroll_to_top();
} else {
self.app.data.radarr_data.collections.scroll_to_top()
}
}
ActiveRadarrBlock::SearchCollection => self.app.data.radarr_data.search.scroll_home(),
ActiveRadarrBlock::FilterCollections => self.app.data.radarr_data.filter.scroll_home(),
_ => (),
}
}
fn handle_end(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self
.app
.data
.radarr_data
.filtered_collections
.scroll_to_bottom();
} else {
self.app.data.radarr_data.collections.scroll_to_bottom()
}
}
ActiveRadarrBlock::SearchCollection => self.app.data.radarr_data.search.reset_offset(),
ActiveRadarrBlock::FilterCollections => self.app.data.radarr_data.filter.reset_offset(),
_ => (),
}
}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => handle_change_tab_left_right_keys(self.app, self.key),
ActiveRadarrBlock::UpdateAllCollectionsPrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::SearchCollection => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search)
}
ActiveRadarrBlock::FilterCollections => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter)
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => self
.app
.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()),
ActiveRadarrBlock::SearchCollection => {
if self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
let selected_index = search_table(
self.app,
&self.app.data.radarr_data.collections.items.clone(),
|collection| &collection.title.text,
);
self
.app
.data
.radarr_data
.collections
.select_index(selected_index);
} else {
let selected_index = search_table(
self.app,
&self.app.data.radarr_data.filtered_collections.items.clone(),
|collection| &collection.title.text,
);
self
.app
.data
.radarr_data
.filtered_collections
.select_index(selected_index);
}
}
ActiveRadarrBlock::FilterCollections => {
let filtered_collections = filter_table(
self.app,
&self.app.data.radarr_data.collections.items.clone(),
|collection| &collection.title.text,
);
if !filtered_collections.is_empty() {
self
.app
.data
.radarr_data
.filtered_collections
.set_items(filtered_collections);
}
}
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
}
self.app.pop_navigation_stack();
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::FilterCollections => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_filter();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::SearchCollection => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_search();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
}
_ => {
self.app.data.radarr_data.reset_search();
self.app.data.radarr_data.reset_filter();
handle_clear_errors(self.app);
}
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Collections => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.search.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
self.app.data.radarr_data.is_searching = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
self.app.data.radarr_data.is_filtering = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.edit.key => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditCollectionPrompt,
Some(ActiveRadarrBlock::Collections),
)
.into(),
);
self.app.data.radarr_data.populate_edit_collection_fields();
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS);
}
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::SearchCollection => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.search)
}
ActiveRadarrBlock::FilterCollections => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.filter)
}
_ => (),
}
}
}
@@ -0,0 +1,282 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
use crate::handlers::KeyEventHandler;
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::models::radarr_models::DownloadRecord;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_downloads_scroll,
DownloadsHandler,
downloads,
DownloadRecord,
ActiveRadarrBlock::Downloads,
None,
title
);
}
mod test_handle_home_end {
use crate::models::radarr_models::DownloadRecord;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*;
test_iterable_home_and_end!(
test_downloads_home_end,
DownloadsHandler,
downloads,
DownloadRecord,
ActiveRadarrBlock::Downloads,
None,
title
);
}
mod test_handle_delete {
use pretty_assertions::assert_eq;
use crate::assert_delete_prompt;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
#[test]
fn test_delete_download_prompt() {
assert_delete_prompt!(
DownloadsHandler,
ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt
);
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
#[test]
fn test_downloads_tab_left() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(1);
DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.left.key,
&mut app,
&ActiveRadarrBlock::Downloads,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Movies.into()
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
}
#[test]
fn test_downloads_tab_right() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(1);
DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.right.key,
&mut app,
&ActiveRadarrBlock::Downloads,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Collections.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
}
#[rstest]
fn test_downloads_left_right_prompt_toggle(
#[values(
ActiveRadarrBlock::DeleteDownloadPrompt,
ActiveRadarrBlock::UpdateDownloadsPrompt
)]
active_radarr_block: ActiveRadarrBlock,
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::default();
DownloadsHandler::with(&key, &mut app, &active_radarr_block, &None).handle();
assert!(app.data.radarr_data.prompt_confirm);
DownloadsHandler::with(&key, &mut app, &active_radarr_block, &None).handle();
assert!(!app.data.radarr_data.prompt_confirm);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::network::radarr_network::RadarrEvent;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[rstest]
#[case(
ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt,
RadarrEvent::DeleteDownload
)]
#[case(
ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::UpdateDownloadsPrompt,
RadarrEvent::UpdateDownloads
)]
fn test_downloads_prompt_confirm_submit(
#[case] base_route: ActiveRadarrBlock,
#[case] prompt_block: ActiveRadarrBlock,
#[case] expected_action: RadarrEvent,
) {
let mut app = App::default();
app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into());
DownloadsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle();
assert!(app.data.radarr_data.prompt_confirm);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(expected_action)
);
assert_eq!(app.get_current_route(), &base_route.into());
}
#[rstest]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)]
fn test_downloads_prompt_decline_submit(
#[case] base_route: ActiveRadarrBlock,
#[case] prompt_block: ActiveRadarrBlock,
) {
let mut app = App::default();
app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into());
DownloadsHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle();
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(app.get_current_route(), &base_route.into());
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[rstest]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)]
fn test_downloads_prompt_blocks_esc(
#[case] base_block: ActiveRadarrBlock,
#[case] prompt_block: ActiveRadarrBlock,
) {
let mut app = App::default();
app.push_navigation_stack(base_block.into());
app.push_navigation_stack(prompt_block.into());
app.data.radarr_data.prompt_confirm = true;
DownloadsHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle();
assert_eq!(app.get_current_route(), &base_block.into());
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_default_esc() {
let mut app = App::default();
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
DownloadsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert!(app.error.text.is_empty());
}
}
mod test_handle_key_char {
use pretty_assertions::assert_eq;
use crate::assert_refresh_key;
use super::*;
#[test]
fn test_update_downloads_key() {
let mut app = App::default();
DownloadsHandler::with(
&DEFAULT_KEYBINDINGS.update.key,
&mut app,
&ActiveRadarrBlock::Downloads,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::UpdateDownloadsPrompt.into()
);
}
#[test]
fn test_refresh_downloads_key() {
assert_refresh_key!(DownloadsHandler, ActiveRadarrBlock::Downloads);
}
}
#[test]
fn test_downloads_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if DOWNLOADS_BLOCKS.contains(&active_radarr_block) {
assert!(DownloadsHandler::accepts(&active_radarr_block));
} else {
assert!(!DownloadsHandler::accepts(&active_radarr_block));
}
})
}
}
@@ -0,0 +1,132 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent;
#[cfg(test)]
#[path = "downloads_handler_tests.rs"]
mod downloads_handler_tests;
pub(super) struct DownloadsHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
DOWNLOADS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>,
) -> DownloadsHandler<'a, 'b> {
DownloadsHandler {
key,
app,
active_radarr_block: active_block,
_context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_up()
}
}
fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_down()
}
}
fn handle_home(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_to_top()
}
}
fn handle_end(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self.app.data.radarr_data.downloads.scroll_to_bottom()
}
}
fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into())
}
}
fn handle_left_right_action(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Downloads => handle_change_tab_left_right_keys(self.app, self.key),
ActiveRadarrBlock::DeleteDownloadPrompt | ActiveRadarrBlock::UpdateDownloadsPrompt => {
handle_prompt_toggle(self.app, self.key)
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::DeleteDownloadPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::UpdateDownloadsPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
}
self.app.pop_navigation_stack();
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::DeleteDownloadPrompt | ActiveRadarrBlock::UpdateDownloadsPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
}
_ => handle_clear_errors(self.app),
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
match self.key {
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ => (),
}
}
}
}
@@ -0,0 +1,74 @@
use crate::app::radarr::{ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
#[cfg(test)]
#[path = "./edit_indexer_settings_handler_tests.rs"]
mod edit_indexer_settings_handler_tests;
pub(super) struct IndexerSettingsHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
INDEXER_SETTINGS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>,
) -> IndexerSettingsHandler<'a, 'b> {
IndexerSettingsHandler {
key,
app,
active_radarr_block: active_block,
_context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsPrompt {
self.app.data.radarr_data.selected_block.previous()
}
}
fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsPrompt {
self.app.data.radarr_data.selected_block.next()
}
}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {}
fn handle_submit(&mut self) {}
fn handle_esc(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::IndexerSettingsPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
self.app.data.radarr_data.indexer_settings = None;
}
_ => self.app.pop_navigation_stack(), // Need to tweak this still and add unit tests
}
}
fn handle_char_key_event(&mut self) {}
}
@@ -0,0 +1,98 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
use crate::handlers::KeyEventHandler;
mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::app::radarr::INDEXER_SETTINGS_SELECTION_BLOCKS;
use crate::models::BlockSelectionState;
use super::*;
#[rstest]
fn test_edit_indexer_settings_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) {
let mut app = App::default();
app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.radarr_data.selected_block.next();
IndexerSettingsHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::IndexerSettingsPrompt,
&None,
)
.handle();
if key == Key::Up {
assert_eq!(
app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::IndexerSettingsMinimumAgeInput
);
} else {
assert_eq!(
app.data.radarr_data.selected_block.get_active_block(),
&ActiveRadarrBlock::IndexerSettingsMaximumSizeInput
);
}
}
}
mod test_handle_home_end {}
mod test_handle_left_right_action {}
mod test_handle_submit {}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data;
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_edit_indexer_settings_prompt_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.data.radarr_data = create_test_radarr_data();
IndexerSettingsHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::IndexerSettingsPrompt,
&None,
)
.handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.indexer_settings, None);
}
}
mod test_handle_key_char {}
#[test]
fn test_indexer_settings_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block) {
assert!(IndexerSettingsHandler::accepts(&active_radarr_block));
} else {
assert!(!IndexerSettingsHandler::accepts(&active_radarr_block));
}
})
}
}
@@ -0,0 +1,342 @@
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
use crate::handlers::KeyEventHandler;
use crate::test_handler_delegation;
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::models::radarr_models::Indexer;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_indexers_scroll,
IndexersHandler,
indexers,
simple_stateful_iterable_vec!(Indexer, String, protocol),
ActiveRadarrBlock::Indexers,
None,
protocol
);
}
mod test_handle_home_end {
use crate::models::radarr_models::Indexer;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*;
test_iterable_home_and_end!(
test_indexers_home_end,
IndexersHandler,
indexers,
extended_stateful_iterable_vec!(Indexer, String, protocol),
ActiveRadarrBlock::Indexers,
None,
protocol
);
}
mod test_handle_delete {
use pretty_assertions::assert_eq;
use crate::assert_delete_prompt;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
#[test]
fn test_delete_indexer_prompt() {
assert_delete_prompt!(
IndexersHandler,
ActiveRadarrBlock::Indexers,
ActiveRadarrBlock::DeleteIndexerPrompt
);
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
#[test]
fn test_indexers_tab_left() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(4);
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.left.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::RootFolders.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
}
#[test]
fn test_indexers_tab_right() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(4);
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.right.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::System.into()
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into());
}
#[rstest]
fn test_left_right_delete_indexer_prompt_toggle(
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::default();
IndexersHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
IndexersHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use crate::network::radarr_network::RadarrEvent;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_indexer_submit_aka_edit() {
let mut app = App::default();
IndexersHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::EditIndexer.into()
);
}
#[test]
fn test_delete_indexer_prompt_confirm_submit() {
let mut app = App::default();
app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteIndexer)
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
}
#[test]
fn test_prompt_decline_submit() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_delete_indexer_prompt_block_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
app.data.radarr_data.prompt_confirm = true;
IndexersHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::DeleteIndexerPrompt,
&None,
)
.handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_default_esc() {
let mut app = App::default();
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
IndexersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
assert!(app.error.text.is_empty());
}
}
mod test_handle_key_char {
use pretty_assertions::assert_eq;
use crate::app::radarr::INDEXER_SETTINGS_SELECTION_BLOCKS;
use crate::assert_refresh_key;
use super::*;
#[test]
fn test_indexer_add() {
let mut app = App::default();
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.add.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::AddIndexer.into()
);
}
#[test]
fn test_refresh_indexers_key() {
assert_refresh_key!(IndexersHandler, ActiveRadarrBlock::Indexers);
}
#[test]
fn test_indexer_settings_key() {
let mut app = App::default();
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.settings.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::IndexerSettingsPrompt.into()
);
assert_eq!(
app.data.radarr_data.selected_block.blocks,
&INDEXER_SETTINGS_SELECTION_BLOCKS
);
}
}
#[rstest]
fn test_delegates_indexer_settings_blocks_to_indexer_settings_handler(
#[values(
ActiveRadarrBlock::IndexerSettingsPrompt,
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
ActiveRadarrBlock::IndexerSettingsConfirmPrompt,
ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
ActiveRadarrBlock::IndexerSettingsRetentionInput,
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags,
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
IndexersHandler,
ActiveRadarrBlock::Indexers,
active_radarr_block
);
}
#[test]
fn test_indexers_handler_accepts() {
let mut indexers_blocks = Vec::new();
indexers_blocks.extend(INDEXERS_BLOCKS);
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if indexers_blocks.contains(&active_radarr_block) {
assert!(IndexersHandler::accepts(&active_radarr_block));
} else {
assert!(!IndexersHandler::accepts(&active_radarr_block));
}
})
}
}
@@ -0,0 +1,148 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent;
mod edit_indexer_settings_handler;
#[cfg(test)]
#[path = "indexers_handler_tests.rs"]
mod indexers_handler_tests;
pub(super) struct IndexersHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, 'b> {
fn handle(&mut self) {
match self.active_radarr_block {
_ if IndexerSettingsHandler::accepts(self.active_radarr_block) => {
IndexerSettingsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ => self.handle_key_event(),
}
}
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
IndexerSettingsHandler::accepts(active_block) || INDEXERS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
) -> IndexersHandler<'a, 'b> {
IndexersHandler {
key,
app,
active_radarr_block: active_block,
context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_up();
}
}
fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_down();
}
}
fn handle_home(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_to_top();
}
}
fn handle_end(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self.app.data.radarr_data.indexers.scroll_to_bottom();
}
}
fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
}
}
fn handle_left_right_action(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Indexers => handle_change_tab_left_right_keys(self.app, self.key),
ActiveRadarrBlock::DeleteIndexerPrompt => handle_prompt_toggle(self.app, self.key),
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::DeleteIndexerPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::Indexers => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::EditIndexer.into());
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::DeleteIndexerPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
}
_ => handle_clear_errors(self.app),
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
match self.key {
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddIndexer.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.settings.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into());
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS);
}
_ => (),
}
}
}
}
@@ -1,5 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS};
use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS};
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::{BlockSelectionState, Scrollable, StatefulTable};
use crate::network::radarr_network::RadarrEvent;
@@ -17,6 +17,10 @@ pub(super) struct AddMovieHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
ADD_MOVIE_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -1,12 +1,13 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::add_movie_handler::AddMovieHandler;
use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::{
AddMovieSearchResult, MinimumAvailability, Monitor, RootFolder,
@@ -724,4 +725,15 @@ mod tests {
assert_str_eq!(app.data.radarr_data.edit_tags.text, "h");
}
}
#[test]
fn test_add_movie_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(AddMovieHandler::accepts(&active_radarr_block));
} else {
assert!(!AddMovieHandler::accepts(&active_radarr_block));
}
});
}
}
@@ -1,4 +1,4 @@
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -16,6 +16,10 @@ pub(super) struct DeleteMovieHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
DELETE_MOVIE_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -1,10 +1,12 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::delete_movie_handler::DeleteMovieHandler;
use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler;
use crate::handlers::KeyEventHandler;
mod test_handle_scroll_up_and_down {
@@ -197,4 +199,15 @@ mod tests {
assert!(!app.data.radarr_data.add_list_exclusion);
}
}
#[test]
fn test_delete_movie_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(DeleteMovieHandler::accepts(&active_radarr_block));
} else {
assert!(!DeleteMovieHandler::accepts(&active_radarr_block));
}
});
}
}
@@ -1,5 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -19,6 +19,10 @@ pub(super) struct EditMovieHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
EDIT_MOVIE_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -1,12 +1,13 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler;
use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::MinimumAvailability;
@@ -503,4 +504,15 @@ mod tests {
assert_str_eq!(app.data.radarr_data.edit_tags.text, "h");
}
}
#[test]
fn test_edit_movie_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(EditMovieHandler::accepts(&active_radarr_block));
} else {
assert!(!EditMovieHandler::accepts(&active_radarr_block));
}
});
}
}
@@ -0,0 +1,699 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, DELETE_MOVIE_BLOCKS, EDIT_MOVIE_BLOCKS, LIBRARY_BLOCKS,
MOVIE_DETAILS_BLOCKS,
};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::library::LibraryHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::Movie;
use crate::models::HorizontallyScrollableText;
use crate::test_handler_delegation;
mod test_handle_scroll_up_and_down {
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_movies_scroll,
LibraryHandler,
movies,
simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText),
ActiveRadarrBlock::Movies,
None,
title,
to_string
);
test_iterable_scroll!(
test_filtered_movies_scroll,
LibraryHandler,
filtered_movies,
simple_stateful_iterable_vec!(Movie, HorizontallyScrollableText),
ActiveRadarrBlock::Movies,
None,
title,
to_string
);
}
mod test_handle_home_end {
use pretty_assertions::assert_eq;
use crate::{
extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys,
};
use super::*;
test_iterable_home_and_end!(
test_movies_home_end,
LibraryHandler,
movies,
extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText),
ActiveRadarrBlock::Movies,
None,
title,
to_string
);
test_iterable_home_and_end!(
test_filtered_movies_home_end,
LibraryHandler,
filtered_movies,
extended_stateful_iterable_vec!(Movie, HorizontallyScrollableText),
ActiveRadarrBlock::Movies,
None,
title,
to_string
);
#[test]
fn test_movie_search_box_home_end_keys() {
test_text_box_home_end_keys!(LibraryHandler, ActiveRadarrBlock::SearchMovie, search);
}
#[test]
fn test_movie_filter_box_home_end_keys() {
test_text_box_home_end_keys!(LibraryHandler, ActiveRadarrBlock::FilterMovies, filter);
}
}
mod test_handle_delete {
use pretty_assertions::assert_eq;
use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS;
use crate::assert_delete_prompt;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
#[test]
fn test_movies_delete() {
let mut app = App::default();
assert_delete_prompt!(
LibraryHandler,
app,
ActiveRadarrBlock::Movies,
ActiveRadarrBlock::DeleteMoviePrompt
);
assert_eq!(
app.data.radarr_data.selected_block.blocks,
&DELETE_MOVIE_SELECTION_BLOCKS
);
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::test_text_box_left_right_keys;
use super::*;
#[test]
fn test_movie_tab_left() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(0);
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.left.key,
&mut app,
&ActiveRadarrBlock::Movies,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::System.into()
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into());
}
#[test]
fn test_movie_tab_right() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(0);
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.right.key,
&mut app,
&ActiveRadarrBlock::Movies,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Downloads.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Downloads.into()
);
}
#[rstest]
fn test_left_right_update_all_movies_prompt_toggle(
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::default();
LibraryHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::UpdateAllMoviesPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
LibraryHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::UpdateAllMoviesPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_movie_search_box_left_right_keys() {
test_text_box_left_right_keys!(LibraryHandler, ActiveRadarrBlock::SearchMovie, search);
}
#[test]
fn test_movie_filter_box_left_right_keys() {
test_text_box_left_right_keys!(LibraryHandler, ActiveRadarrBlock::FilterMovies, filter);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use crate::extended_stateful_iterable_vec;
use crate::network::radarr_network::RadarrEvent;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_movie_details_submit() {
let mut app = App::default();
LibraryHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::MovieDetails.into()
);
}
#[test]
fn test_search_movie_submit() {
let mut app = App::default();
app
.data
.radarr_data
.movies
.set_items(extended_stateful_iterable_vec!(
Movie,
HorizontallyScrollableText
));
app.data.radarr_data.search = "Test 2".to_owned().into();
LibraryHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::SearchMovie,
&None,
)
.handle();
assert_str_eq!(
app.data.radarr_data.movies.current_selection().title.text,
"Test 2"
);
}
#[test]
fn test_search_filtered_movies_submit() {
let mut app = App::default();
app
.data
.radarr_data
.filtered_movies
.set_items(extended_stateful_iterable_vec!(
Movie,
HorizontallyScrollableText
));
app.data.radarr_data.search = "Test 2".to_owned().into();
LibraryHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::SearchMovie,
&None,
)
.handle();
assert_str_eq!(
app
.data
.radarr_data
.filtered_movies
.current_selection()
.title
.text,
"Test 2"
);
}
#[test]
fn test_filter_movies_submit() {
let mut app = App::default();
app
.data
.radarr_data
.movies
.set_items(extended_stateful_iterable_vec!(
Movie,
HorizontallyScrollableText
));
app.data.radarr_data.filter = "Test".to_owned().into();
LibraryHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::FilterMovies,
&None,
)
.handle();
assert_eq!(app.data.radarr_data.filtered_movies.items.len(), 3);
assert_str_eq!(
app
.data
.radarr_data
.filtered_movies
.current_selection()
.title
.text,
"Test 1"
);
}
#[test]
fn test_update_all_movies_prompt_confirm_submit() {
let mut app = App::default();
app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
LibraryHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::UpdateAllMoviesPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::UpdateAllMovies)
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
}
#[test]
fn test_update_all_movies_prompt_decline_submit() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
LibraryHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::UpdateAllMoviesPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data;
use crate::{assert_filter_reset, assert_search_reset};
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_search_movie_block_esc() {
let mut app = App::default();
app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
app.data.radarr_data = create_test_radarr_data();
LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::SearchMovie, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert_search_reset!(app.data.radarr_data);
}
#[test]
fn test_filter_movies_block_esc() {
let mut app = App::default();
app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
app.data.radarr_data = create_test_radarr_data();
LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::FilterMovies, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert_filter_reset!(app.data.radarr_data);
}
#[test]
fn test_update_all_movies_prompt_blocks_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
app.data.radarr_data.prompt_confirm = true;
LibraryHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::UpdateAllMoviesPrompt,
&None,
)
.handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_default_esc() {
let mut app = App::default();
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.data.radarr_data = create_test_radarr_data();
LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(app.error.text.is_empty());
assert_search_reset!(app.data.radarr_data);
assert_filter_reset!(app.data.radarr_data);
}
}
mod test_handle_key_char {
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::Number;
use strum::IntoEnumIterator;
use crate::app::radarr::radarr_test_utils::utils::create_test_radarr_data;
use crate::app::radarr::RadarrData;
use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS;
use crate::models::radarr_models::MinimumAvailability;
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable;
use crate::{assert_refresh_key, test_edit_movie_key};
use super::*;
#[test]
fn test_search_movies_key() {
let mut app = App::default();
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.search.key,
&mut app,
&ActiveRadarrBlock::Movies,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::SearchMovie.into()
);
assert!(app.data.radarr_data.is_searching);
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_filter_movies_key() {
let mut app = App::default();
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.filter.key,
&mut app,
&ActiveRadarrBlock::Movies,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::FilterMovies.into()
);
assert!(app.data.radarr_data.is_filtering);
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_movie_add() {
let mut app = App::default();
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.add.key,
&mut app,
&ActiveRadarrBlock::Movies,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::AddMovieSearchInput.into()
);
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_movie_edit_key() {
test_edit_movie_key!(
LibraryHandler,
ActiveRadarrBlock::Movies,
ActiveRadarrBlock::Movies
);
}
#[test]
fn test_update_all_movies_key() {
let mut app = App::default();
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.update.key,
&mut app,
&ActiveRadarrBlock::Movies,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::UpdateAllMoviesPrompt.into()
);
}
#[test]
fn test_refresh_movies_key() {
assert_refresh_key!(LibraryHandler, ActiveRadarrBlock::Movies);
}
#[test]
fn test_search_movies_box_backspace_key() {
let mut app = App::default();
app.data.radarr_data.search = "Test".to_owned().into();
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key,
&mut app,
&ActiveRadarrBlock::SearchMovie,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.search.text, "Tes");
}
#[test]
fn test_filter_movies_box_backspace_key() {
let mut app = App::default();
app.data.radarr_data.filter = "Test".to_owned().into();
LibraryHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key,
&mut app,
&ActiveRadarrBlock::FilterMovies,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.filter.text, "Tes");
}
#[test]
fn test_search_movies_box_char_key() {
let mut app = App::default();
LibraryHandler::with(
&Key::Char('h'),
&mut app,
&ActiveRadarrBlock::SearchMovie,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.search.text, "h");
}
#[test]
fn test_filter_movies_box_char_key() {
let mut app = App::default();
LibraryHandler::with(
&Key::Char('h'),
&mut app,
&ActiveRadarrBlock::FilterMovies,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.filter.text, "h");
}
}
#[rstest]
fn test_delegates_add_movie_blocks_to_add_movie_handler(
#[values(
ActiveRadarrBlock::AddMovieSearchInput,
ActiveRadarrBlock::AddMovieSearchResults,
ActiveRadarrBlock::AddMoviePrompt,
ActiveRadarrBlock::AddMovieSelectMonitor,
ActiveRadarrBlock::AddMovieSelectMinimumAvailability,
ActiveRadarrBlock::AddMovieSelectQualityProfile,
ActiveRadarrBlock::AddMovieSelectRootFolder,
ActiveRadarrBlock::AddMovieAlreadyInLibrary,
ActiveRadarrBlock::AddMovieTagsInput
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
LibraryHandler,
ActiveRadarrBlock::Movies,
active_radarr_block
);
}
#[rstest]
fn test_delegates_movie_details_blocks_to_movie_details_handler(
#[values(
ActiveRadarrBlock::MovieDetails,
ActiveRadarrBlock::MovieHistory,
ActiveRadarrBlock::FileInfo,
ActiveRadarrBlock::Cast,
ActiveRadarrBlock::Crew,
ActiveRadarrBlock::AutomaticallySearchMoviePrompt,
ActiveRadarrBlock::UpdateAndScanPrompt,
ActiveRadarrBlock::ManualSearch,
ActiveRadarrBlock::ManualSearchConfirmPrompt
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
LibraryHandler,
ActiveRadarrBlock::Movies,
active_radarr_block
);
}
#[rstest]
fn test_delegates_edit_movie_blocks_to_edit_movie_handler(
#[values(
ActiveRadarrBlock::EditMoviePrompt,
ActiveRadarrBlock::EditMoviePathInput,
ActiveRadarrBlock::EditMovieSelectMinimumAvailability,
ActiveRadarrBlock::EditMovieSelectQualityProfile,
ActiveRadarrBlock::EditMovieTagsInput
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
LibraryHandler,
ActiveRadarrBlock::Movies,
active_radarr_block
);
}
#[test]
fn test_delegates_delete_movie_blocks_to_delete_movie_handler() {
test_handler_delegation!(
LibraryHandler,
ActiveRadarrBlock::Movies,
ActiveRadarrBlock::DeleteMoviePrompt
);
}
#[test]
fn test_library_handler_accepts() {
let mut library_handler_blocks = Vec::new();
library_handler_blocks.extend(LIBRARY_BLOCKS);
library_handler_blocks.extend(ADD_MOVIE_BLOCKS);
library_handler_blocks.extend(DELETE_MOVIE_BLOCKS);
library_handler_blocks.extend(EDIT_MOVIE_BLOCKS);
library_handler_blocks.extend(MOVIE_DETAILS_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if library_handler_blocks.contains(&active_radarr_block) {
assert!(LibraryHandler::accepts(&active_radarr_block));
} else {
assert!(!LibraryHandler::accepts(&active_radarr_block));
}
});
}
}
+298
View File
@@ -0,0 +1,298 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{
ActiveRadarrBlock, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, LIBRARY_BLOCKS,
};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler;
use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler;
use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler;
use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler;
use crate::handlers::radarr_handlers::{
filter_table, handle_change_tab_left_right_keys, search_table,
};
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
mod add_movie_handler;
mod delete_movie_handler;
mod edit_movie_handler;
mod movie_details_handler;
#[cfg(test)]
#[path = "library_handler_tests.rs"]
mod library_handler_tests;
pub(super) struct LibraryHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, 'b> {
fn handle(&mut self) {
match self.active_radarr_block {
_ if AddMovieHandler::accepts(self.active_radarr_block) => {
AddMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle();
}
_ if DeleteMovieHandler::accepts(self.active_radarr_block) => {
DeleteMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ if EditMovieHandler::accepts(self.active_radarr_block) => {
EditMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle();
}
_ if MovieDetailsHandler::accepts(self.active_radarr_block) => {
MovieDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ => self.handle_key_event(),
}
}
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
AddMovieHandler::accepts(active_block)
|| DeleteMovieHandler::accepts(active_block)
|| EditMovieHandler::accepts(active_block)
|| MovieDetailsHandler::accepts(active_block)
|| LIBRARY_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
) -> LibraryHandler<'a, 'b> {
LibraryHandler {
key,
app,
active_radarr_block: active_block,
context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Movies {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_up();
} else {
self.app.data.radarr_data.movies.scroll_up()
}
}
}
fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Movies {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_down();
} else {
self.app.data.radarr_data.movies.scroll_down()
}
}
}
fn handle_home(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies => {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_to_top();
} else {
self.app.data.radarr_data.movies.scroll_to_top()
}
}
ActiveRadarrBlock::SearchMovie => {
self.app.data.radarr_data.search.scroll_home();
}
ActiveRadarrBlock::FilterMovies => {
self.app.data.radarr_data.filter.scroll_home();
}
_ => (),
}
}
fn handle_end(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies => {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_to_bottom();
} else {
self.app.data.radarr_data.movies.scroll_to_bottom()
}
}
ActiveRadarrBlock::SearchMovie => self.app.data.radarr_data.search.reset_offset(),
ActiveRadarrBlock::FilterMovies => self.app.data.radarr_data.filter.reset_offset(),
_ => (),
}
}
fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Movies {
self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
}
}
fn handle_left_right_action(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies => handle_change_tab_left_right_keys(self.app, self.key),
ActiveRadarrBlock::UpdateAllMoviesPrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::SearchMovie => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search)
}
ActiveRadarrBlock::FilterMovies => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter)
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies => self
.app
.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()),
ActiveRadarrBlock::SearchMovie => {
if self.app.data.radarr_data.filtered_movies.items.is_empty() {
let selected_index = search_table(
self.app,
&self.app.data.radarr_data.movies.items.clone(),
|movie| &movie.title.text,
);
self
.app
.data
.radarr_data
.movies
.select_index(selected_index);
} else {
let selected_index = search_table(
self.app,
&self.app.data.radarr_data.filtered_movies.items.clone(),
|movie| &movie.title.text,
);
self
.app
.data
.radarr_data
.filtered_movies
.select_index(selected_index);
};
}
ActiveRadarrBlock::FilterMovies => {
let filtered_movies = filter_table(
self.app,
&self.app.data.radarr_data.movies.items.clone(),
|movie| &movie.title.text,
);
if !filtered_movies.is_empty() {
self
.app
.data
.radarr_data
.filtered_movies
.set_items(filtered_movies);
}
}
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
}
self.app.pop_navigation_stack();
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::FilterMovies => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_filter();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::SearchMovie => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_search();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
}
_ => {
self.app.data.radarr_data.reset_search();
self.app.data.radarr_data.reset_filter();
handle_clear_errors(self.app);
}
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Movies => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.search.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
self.app.data.radarr_data.is_searching = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
self.app.data.radarr_data.is_filtering = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.edit.key => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditMoviePrompt,
Some(ActiveRadarrBlock::Movies),
)
.into(),
);
self.app.data.radarr_data.populate_edit_movie_fields();
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS);
}
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::SearchMovie => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.search)
}
ActiveRadarrBlock::FilterMovies => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.filter)
}
_ => (),
}
}
}
@@ -4,7 +4,7 @@ use serde_json::Number;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS};
use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS, MOVIE_DETAILS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -24,6 +24,10 @@ pub(super) struct MovieDetailsHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
MOVIE_DETAILS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -292,7 +296,7 @@ fn sort_releases_by_selected_field(
.cmp(release_b.size.as_u64().as_ref().unwrap())
},
ReleaseField::Peers => |release_a, release_b| {
let default_number = Number::from(i64::MAX);
let default_number = Number::from(i64::max_value());
let seeder_a = release_a
.seeders
.as_ref()
@@ -3,12 +3,13 @@ mod tests {
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use serde_json::Number;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::movie_details_handler::{
use crate::handlers::radarr_handlers::library::movie_details_handler::{
sort_releases_by_selected_field, MovieDetailsHandler,
};
use crate::handlers::KeyEventHandler;
@@ -415,7 +416,6 @@ mod tests {
use crate::app::radarr::RadarrData;
use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS;
use crate::models::radarr_models::{MinimumAvailability, Movie};
use crate::models::BlockSelectionState;
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable;
use crate::test_edit_movie_key;
@@ -643,4 +643,15 @@ mod tests {
vec![release_a, release_b, release_c]
}
#[test]
fn test_movie_details_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(MovieDetailsHandler::accepts(&active_radarr_block));
} else {
assert!(!MovieDetailsHandler::accepts(&active_radarr_block));
}
});
}
}
+90 -632
View File
@@ -1,30 +1,21 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS,
DELETE_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, FILTER_BLOCKS, MOVIE_DETAILS_BLOCKS,
SEARCH_BLOCKS, SYSTEM_DETAILS_BLOCKS,
};
use crate::handlers::radarr_handlers::add_movie_handler::AddMovieHandler;
use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::radarr_handlers::delete_movie_handler::DeleteMovieHandler;
use crate::handlers::radarr_handlers::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler;
use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler;
use crate::handlers::radarr_handlers::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable};
use crate::network::radarr_network::RadarrEvent;
use crate::app::radarr::ActiveRadarrBlock;
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
use crate::handlers::radarr_handlers::library::LibraryHandler;
use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
use crate::handlers::radarr_handlers::system::SystemHandler;
use crate::handlers::KeyEventHandler;
use crate::utils::strip_non_search_characters;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
use crate::{App, Key};
mod add_movie_handler;
mod collection_details_handler;
mod delete_movie_handler;
mod edit_collection_handler;
mod edit_movie_handler;
mod movie_details_handler;
mod system_details_handler;
mod collections;
mod downloads;
mod indexers;
mod library;
mod root_folders;
mod system;
#[cfg(test)]
#[path = "radarr_handler_tests.rs"]
@@ -44,36 +35,34 @@ pub(super) struct RadarrHandler<'a, 'b> {
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b> {
fn handle(&mut self) {
match self.active_radarr_block {
_ if MOVIE_DETAILS_BLOCKS.contains(self.active_radarr_block) => {
MovieDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
_ if LibraryHandler::accepts(self.active_radarr_block) => {
LibraryHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle();
}
_ if CollectionsHandler::accepts(self.active_radarr_block) => {
CollectionsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if COLLECTION_DETAILS_BLOCKS.contains(self.active_radarr_block) => {
CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
_ if IndexersHandler::accepts(self.active_radarr_block) => {
IndexersHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if SystemHandler::accepts(self.active_radarr_block) => {
SystemHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if DownloadsHandler::accepts(self.active_radarr_block) => {
DownloadsHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if RootFoldersHandler::accepts(self.active_radarr_block) => {
RootFoldersHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if ADD_MOVIE_BLOCKS.contains(self.active_radarr_block) => {
AddMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if EDIT_MOVIE_BLOCKS.contains(self.active_radarr_block) => {
EditMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if DELETE_MOVIE_BLOCKS.contains(self.active_radarr_block) => {
DeleteMovieHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if EDIT_COLLECTION_BLOCKS.contains(self.active_radarr_block) => {
EditCollectionHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ if SYSTEM_DETAILS_BLOCKS.contains(self.active_radarr_block) => {
SystemDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle();
}
_ => self.handle_key_event(),
}
}
fn accepts(_active_block: &'a ActiveRadarrBlock) -> bool {
true
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -92,608 +81,77 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
self.key
}
fn handle_scroll_up(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self.app.data.radarr_data.filtered_collections.scroll_up();
} else {
self.app.data.radarr_data.collections.scroll_up()
}
}
ActiveRadarrBlock::Movies => {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_up();
} else {
self.app.data.radarr_data.movies.scroll_up()
}
}
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_up(),
ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_up(),
ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_up(),
_ => (),
}
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self.app.data.radarr_data.filtered_collections.scroll_down();
} else {
self.app.data.radarr_data.collections.scroll_down()
}
}
ActiveRadarrBlock::Movies => {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_down();
} else {
self.app.data.radarr_data.movies.scroll_down()
}
}
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_down(),
ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_down(),
ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_down(),
_ => (),
}
}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self
.app
.data
.radarr_data
.filtered_collections
.scroll_to_top();
} else {
self.app.data.radarr_data.collections.scroll_to_top()
}
}
ActiveRadarrBlock::Movies => {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_to_top();
} else {
self.app.data.radarr_data.movies.scroll_to_top()
}
}
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_top(),
ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_to_top(),
ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_top(),
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => {
self.app.data.radarr_data.search.scroll_home()
}
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => {
self.app.data.radarr_data.filter.scroll_home()
}
ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.scroll_home(),
_ => (),
}
}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Collections => {
if !self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
self
.app
.data
.radarr_data
.filtered_collections
.scroll_to_bottom();
} else {
self.app.data.radarr_data.collections.scroll_to_bottom()
}
}
ActiveRadarrBlock::Movies => {
if !self.app.data.radarr_data.filtered_movies.items.is_empty() {
self.app.data.radarr_data.filtered_movies.scroll_to_bottom();
} else {
self.app.data.radarr_data.movies.scroll_to_bottom()
}
}
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_bottom(),
ActiveRadarrBlock::Indexers => self.app.data.radarr_data.indexers.scroll_to_bottom(),
ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_bottom(),
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => {
self.app.data.radarr_data.search.reset_offset()
}
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => {
self.app.data.radarr_data.filter.reset_offset()
}
ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.reset_offset(),
_ => (),
}
}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
}
ActiveRadarrBlock::Downloads => self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into()),
ActiveRadarrBlock::RootFolders => self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()),
ActiveRadarrBlock::Indexers => self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()),
_ => (),
}
}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies
| ActiveRadarrBlock::Downloads
| ActiveRadarrBlock::Collections
| ActiveRadarrBlock::RootFolders
| ActiveRadarrBlock::Indexers
| ActiveRadarrBlock::System => match self.key {
_ if *self.key == DEFAULT_KEYBINDINGS.left.key => {
self.app.data.radarr_data.main_tabs.previous();
self
.app
.pop_and_push_navigation_stack(*self.app.data.radarr_data.main_tabs.get_active_route());
}
_ if *self.key == DEFAULT_KEYBINDINGS.right.key => {
self.app.data.radarr_data.main_tabs.next();
self
.app
.pop_and_push_navigation_stack(*self.app.data.radarr_data.main_tabs.get_active_route());
}
_ => (),
},
ActiveRadarrBlock::DeleteDownloadPrompt
| ActiveRadarrBlock::DeleteIndexerPrompt
| ActiveRadarrBlock::DeleteRootFolderPrompt
| ActiveRadarrBlock::UpdateAllMoviesPrompt
| ActiveRadarrBlock::UpdateAllCollectionsPrompt
| ActiveRadarrBlock::UpdateDownloadsPrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::AddRootFolderPrompt => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path)
}
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search)
}
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter)
}
_ => (),
}
}
fn handle_left_right_action(&mut self) {}
fn handle_submit(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::Movies => self
.app
.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()),
ActiveRadarrBlock::Collections => self
.app
.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()),
ActiveRadarrBlock::SearchMovie => {
if self.app.data.radarr_data.filtered_movies.items.is_empty() {
let selected_index = self
.search_table(&self.app.data.radarr_data.movies.items.clone(), |movie| {
&movie.title.text
});
self
.app
.data
.radarr_data
.movies
.select_index(selected_index);
} else {
let selected_index = self.search_table(
&self.app.data.radarr_data.filtered_movies.items.clone(),
|movie| &movie.title.text,
);
self
.app
.data
.radarr_data
.filtered_movies
.select_index(selected_index);
};
}
ActiveRadarrBlock::SearchCollection => {
if self
.app
.data
.radarr_data
.filtered_collections
.items
.is_empty()
{
let selected_index = self.search_table(
&self.app.data.radarr_data.collections.items.clone(),
|collection| &collection.title.text,
);
self
.app
.data
.radarr_data
.collections
.select_index(selected_index);
} else {
let selected_index = self.search_table(
&self.app.data.radarr_data.filtered_collections.items.clone(),
|collection| &collection.title.text,
);
self
.app
.data
.radarr_data
.filtered_collections
.select_index(selected_index);
}
}
ActiveRadarrBlock::FilterMovies => {
let filtered_movies = self
.filter_table(&self.app.data.radarr_data.movies.items.clone(), |movie| {
&movie.title.text
});
fn handle_submit(&mut self) {}
if !filtered_movies.is_empty() {
self
.app
.data
.radarr_data
.filtered_movies
.set_items(filtered_movies);
}
}
ActiveRadarrBlock::FilterCollections => {
let filtered_collections = self.filter_table(
&self.app.data.radarr_data.collections.items.clone(),
|collection| &collection.title.text,
);
fn handle_esc(&mut self) {}
if !filtered_collections.is_empty() {
self
.app
.data
.radarr_data
.filtered_collections
.set_items(filtered_collections);
}
}
ActiveRadarrBlock::DeleteDownloadPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::DeleteRootFolderPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteRootFolder);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::DeleteIndexerPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::UpdateDownloadsPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::AddRootFolderPrompt => {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder);
self.app.data.radarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false;
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::Indexers => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::EditIndexer.into());
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_radarr_block {
_ if FILTER_BLOCKS.contains(self.active_radarr_block) => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_filter();
self.app.should_ignore_quit_key = false;
}
_ if SEARCH_BLOCKS.contains(self.active_radarr_block) => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.reset_search();
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::AddRootFolderPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.edit_path = HorizontallyScrollableText::default();
self.app.data.radarr_data.prompt_confirm = false;
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::DeleteDownloadPrompt
| ActiveRadarrBlock::DeleteIndexerPrompt
| ActiveRadarrBlock::DeleteRootFolderPrompt
| ActiveRadarrBlock::UpdateAllMoviesPrompt
| ActiveRadarrBlock::UpdateAllCollectionsPrompt
| ActiveRadarrBlock::UpdateDownloadsPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
}
_ => {
self.app.data.radarr_data.reset_search();
self.app.data.radarr_data.reset_filter();
handle_clear_errors(self.app);
}
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match *self.active_radarr_block {
ActiveRadarrBlock::Movies => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.search.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
self.app.data.radarr_data.is_searching = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
self.app.data.radarr_data.is_filtering = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.edit.key => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditMoviePrompt,
Some(ActiveRadarrBlock::Movies),
)
.into(),
);
self.app.data.radarr_data.populate_edit_movie_fields();
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS);
}
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::Downloads => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::Indexers => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddIndexer.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.settings.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::IndexerSettings.into());
}
_ => (),
},
ActiveRadarrBlock::Collections => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.search.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
self.app.data.radarr_data.is_searching = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
self.app.data.radarr_data.is_filtering = true;
self.app.should_ignore_quit_key = true;
}
_ if *key == DEFAULT_KEYBINDINGS.edit.key => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditCollectionPrompt,
Some(ActiveRadarrBlock::Collections),
)
.into(),
);
self.app.data.radarr_data.populate_edit_collection_fields();
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS);
}
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::RootFolders => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
self.app.should_ignore_quit_key = true;
}
_ => (),
},
ActiveRadarrBlock::System => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.events.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into());
}
_ if *key == DEFAULT_KEYBINDINGS.logs.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemLogs.into());
self
.app
.data
.radarr_data
.log_details
.set_items(self.app.data.radarr_data.logs.items.to_vec());
self.app.data.radarr_data.log_details.scroll_to_bottom();
}
_ if *key == DEFAULT_KEYBINDINGS.tasks.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
}
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
}
_ => (),
},
ActiveRadarrBlock::AddRootFolderPrompt => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.edit_path)
}
_ if SEARCH_BLOCKS.contains(self.active_radarr_block) => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.search)
}
_ if FILTER_BLOCKS.contains(self.active_radarr_block) => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.filter)
}
_ => (),
}
}
fn handle_char_key_event(&mut self) {}
}
impl<'a, 'b> RadarrHandler<'a, 'b> {
fn search_table<T, F>(&mut self, rows: &[T], field_selection_fn: F) -> Option<usize>
where
F: Fn(&T) -> &str,
{
let search_string = self.app.data.radarr_data.search.drain().to_lowercase();
let search_index = rows.iter().position(|item| {
strip_non_search_characters(field_selection_fn(item)).contains(&search_string)
});
pub fn search_table<T, F>(app: &mut App<'_>, rows: &[T], field_selection_fn: F) -> Option<usize>
where
F: Fn(&T) -> &str,
{
let search_string = app.data.radarr_data.search.drain().to_lowercase();
let search_index = rows.iter().position(|item| {
strip_non_search_characters(field_selection_fn(item)).contains(&search_string)
});
self.app.data.radarr_data.is_searching = false;
self.app.should_ignore_quit_key = false;
app.data.radarr_data.is_searching = false;
app.should_ignore_quit_key = false;
if search_index.is_some() {
self.app.pop_navigation_stack();
}
search_index
if search_index.is_some() {
app.pop_navigation_stack();
}
fn filter_table<T, F>(&mut self, rows: &[T], field_selection_fn: F) -> Vec<T>
where
F: Fn(&T) -> &str,
T: Clone,
{
let filter = strip_non_search_characters(&self.app.data.radarr_data.filter.drain());
let filter_matches: Vec<T> = rows
.iter()
.filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter))
.cloned()
.collect();
search_index
}
self.app.data.radarr_data.is_filtering = false;
self.app.should_ignore_quit_key = false;
pub fn filter_table<T, F>(app: &mut App<'_>, rows: &[T], field_selection_fn: F) -> Vec<T>
where
F: Fn(&T) -> &str,
T: Clone,
{
let filter = strip_non_search_characters(&app.data.radarr_data.filter.drain());
let filter_matches: Vec<T> = rows
.iter()
.filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter))
.cloned()
.collect();
if !filter_matches.is_empty() {
self.app.pop_navigation_stack();
app.data.radarr_data.is_filtering = false;
app.should_ignore_quit_key = false;
if !filter_matches.is_empty() {
app.pop_navigation_stack();
}
filter_matches
}
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: &Key) {
let key_ref = key;
match key_ref {
_ if *key == DEFAULT_KEYBINDINGS.left.key => {
app.data.radarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(*app.data.radarr_data.main_tabs.get_active_route());
}
filter_matches
_ if *key == DEFAULT_KEYBINDINGS.right.key => {
app.data.radarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(*app.data.radarr_data.main_tabs.get_active_route());
}
_ => (),
}
}
@@ -26,7 +26,6 @@ mod utils {
tags: vec![Number::from(1)],
..Movie::default()
}]);
radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS);
app.data.radarr_data = radarr_data;
$handler::with(&DEFAULT_KEYBINDINGS.edit.key, &mut app, &$block, &None).handle();
@@ -66,6 +65,10 @@ mod utils {
assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/movies/Test");
assert_str_eq!(app.data.radarr_data.edit_tags.text, "test");
assert_eq!(app.data.radarr_data.edit_monitored, Some(true));
assert_eq!(
app.data.radarr_data.selected_block.blocks,
&EDIT_MOVIE_SELECTION_BLOCKS
);
};
}
@@ -93,7 +96,6 @@ mod utils {
minimum_availability: MinimumAvailability::Released,
..Collection::default()
}]);
radarr_data.selected_block = BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS);
app.data.radarr_data = radarr_data;
$handler::with(&DEFAULT_KEYBINDINGS.edit.key, &mut app, &$block, &None).handle();
@@ -133,6 +135,10 @@ mod utils {
assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/movies/Test");
assert_eq!(app.data.radarr_data.edit_monitored, Some(true));
assert_eq!(app.data.radarr_data.edit_search_on_add, Some(true));
assert_eq!(
app.data.radarr_data.selected_block.blocks,
&EDIT_COLLECTION_SELECTION_BLOCKS
);
};
}
@@ -146,10 +152,37 @@ mod utils {
assert_eq!(app.get_current_route(), &$expected_block.into());
};
($handler:ident, $block:expr, $expected_block:expr) => {
let mut app = App::default();
$handler::with(&DELETE_KEY, &mut app, &$block, &None).handle();
assert_eq!(app.get_current_route(), &$expected_block.into());
};
($app:expr, $block:expr, $expected_block:expr) => {
RadarrHandler::with(&DELETE_KEY, &mut $app, &$block, &None).handle();
assert_eq!($app.get_current_route(), &$expected_block.into());
};
($handler:ident, $app:expr, $block:expr, $expected_block:expr) => {
$handler::with(&DELETE_KEY, &mut $app, &$block, &None).handle();
assert_eq!($app.get_current_route(), &$expected_block.into());
};
}
#[macro_export]
macro_rules! assert_refresh_key {
($handler:ident, $block:expr) => {
let mut app = App::default();
app.push_navigation_stack($block.into());
$handler::with(&DEFAULT_KEYBINDINGS.refresh.key, &mut app, &$block, &None).handle();
assert_eq!(app.get_current_route(), &$block.into());
assert!(app.should_refresh);
};
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,148 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::{HorizontallyScrollableText, Scrollable};
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
#[cfg(test)]
#[path = "root_folders_handler_tests.rs"]
mod root_folders_handler_tests;
pub(super) struct RootFoldersHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
ROOT_FOLDERS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
_context: &'a Option<ActiveRadarrBlock>,
) -> RootFoldersHandler<'a, 'b> {
RootFoldersHandler {
key,
app,
active_radarr_block: active_block,
_context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::RootFolders {
self.app.data.radarr_data.root_folders.scroll_up()
}
}
fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::RootFolders {
self.app.data.radarr_data.root_folders.scroll_down()
}
}
fn handle_home(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_top(),
ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.scroll_home(),
_ => (),
}
}
fn handle_end(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_bottom(),
ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.reset_offset(),
_ => (),
}
}
fn handle_delete(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::RootFolders {
self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into())
}
}
fn handle_left_right_action(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => handle_change_tab_left_right_keys(self.app, self.key),
ActiveRadarrBlock::DeleteRootFolderPrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::AddRootFolderPrompt => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path)
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::DeleteRootFolderPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteRootFolder);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::AddRootFolderPrompt => {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder);
self.app.data.radarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false;
self.app.pop_navigation_stack();
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_radarr_block {
ActiveRadarrBlock::AddRootFolderPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.edit_path = HorizontallyScrollableText::default();
self.app.data.radarr_data.prompt_confirm = false;
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::DeleteRootFolderPrompt => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false;
}
_ => handle_clear_errors(self.app),
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
self.app.should_ignore_quit_key = true;
}
_ => (),
},
ActiveRadarrBlock::AddRootFolderPrompt => {
handle_text_box_keys!(self, key, self.app.data.radarr_data.edit_path)
}
_ => (),
}
}
}
@@ -0,0 +1,401 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
use crate::handlers::KeyEventHandler;
use crate::models::HorizontallyScrollableText;
mod test_handle_scroll_up_and_down {
use rstest::rstest;
use crate::models::radarr_models::RootFolder;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*;
test_iterable_scroll!(
test_root_folders_scroll,
RootFoldersHandler,
root_folders,
simple_stateful_iterable_vec!(RootFolder, String, path),
ActiveRadarrBlock::RootFolders,
None,
path
);
}
mod test_handle_home_end {
use pretty_assertions::assert_eq;
use crate::models::radarr_models::RootFolder;
use crate::{
extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys,
};
use super::*;
test_iterable_home_and_end!(
test_root_folders_home_end,
RootFoldersHandler,
root_folders,
extended_stateful_iterable_vec!(RootFolder, String, path),
ActiveRadarrBlock::RootFolders,
None,
path
);
#[test]
fn test_add_root_folder_prompt_home_end_keys() {
test_text_box_home_end_keys!(
RootFoldersHandler,
ActiveRadarrBlock::AddRootFolderPrompt,
edit_path
);
}
}
mod test_handle_delete {
use pretty_assertions::assert_eq;
use crate::assert_delete_prompt;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
#[test]
fn test_delete_root_folder_prompt() {
assert_delete_prompt!(
RootFoldersHandler,
ActiveRadarrBlock::RootFolders,
ActiveRadarrBlock::DeleteRootFolderPrompt
);
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::test_text_box_left_right_keys;
use super::*;
#[test]
fn test_root_folders_tab_left() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(3);
RootFoldersHandler::with(
&DEFAULT_KEYBINDINGS.left.key,
&mut app,
&ActiveRadarrBlock::RootFolders,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Collections.into()
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::Collections.into()
);
}
#[test]
fn test_root_folders_tab_right() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(3);
RootFoldersHandler::with(
&DEFAULT_KEYBINDINGS.right.key,
&mut app,
&ActiveRadarrBlock::RootFolders,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Indexers.into()
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
}
#[rstest]
fn test_left_right_delete_root_folder_prompt_toggle(
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::default();
RootFoldersHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::DeleteRootFolderPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
RootFoldersHandler::with(
&key,
&mut app,
&ActiveRadarrBlock::DeleteRootFolderPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_add_root_folder_prompt_left_right_keys() {
test_text_box_left_right_keys!(
RootFoldersHandler,
ActiveRadarrBlock::AddRootFolderPrompt,
edit_path
);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use crate::network::radarr_network::RadarrEvent;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_add_root_folder_prompt_confirm_submit() {
let mut app = App::default();
app.data.radarr_data.prompt_confirm = true;
app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
RootFoldersHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::AddRootFolderPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::AddRootFolder)
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
}
#[test]
fn test_delete_root_folder_prompt_confirm_submit() {
let mut app = App::default();
app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into());
RootFoldersHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::DeleteRootFolderPrompt,
&None,
)
.handle();
assert!(app.data.radarr_data.prompt_confirm);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteRootFolder)
);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
}
#[test]
fn test_delete_root_folder_prompt_decline_submit() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into());
RootFoldersHandler::with(
&SUBMIT_KEY,
&mut app,
&ActiveRadarrBlock::DeleteRootFolderPrompt,
&None,
)
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_delete_root_folder_prompt_block_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into());
app.data.radarr_data.prompt_confirm = true;
RootFoldersHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::DeleteRootFolderPrompt,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
assert!(!app.data.radarr_data.prompt_confirm);
}
#[test]
fn test_add_root_folder_prompt_esc() {
let mut app = App::default();
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
app.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test");
app.should_ignore_quit_key = true;
RootFoldersHandler::with(
&ESC_KEY,
&mut app,
&ActiveRadarrBlock::AddRootFolderPrompt,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
assert!(app.data.radarr_data.edit_path.text.is_empty());
assert!(!app.data.radarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key);
}
#[test]
fn test_default_esc() {
let mut app = App::default();
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
RootFoldersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::RootFolders, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::RootFolders.into()
);
assert!(app.error.text.is_empty());
}
}
mod test_handle_key_char {
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::assert_refresh_key;
use super::*;
#[test]
fn test_root_folder_add() {
let mut app = App::default();
RootFoldersHandler::with(
&DEFAULT_KEYBINDINGS.add.key,
&mut app,
&ActiveRadarrBlock::RootFolders,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::AddRootFolderPrompt.into()
);
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_refresh_root_folders_key() {
assert_refresh_key!(RootFoldersHandler, ActiveRadarrBlock::RootFolders);
}
#[test]
fn test_add_root_folder_prompt_backspace_key() {
let mut app = App::default();
app.data.radarr_data.edit_path = "/nfs/test".to_owned().into();
RootFoldersHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key,
&mut app,
&ActiveRadarrBlock::AddRootFolderPrompt,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/tes");
}
#[test]
fn test_add_root_folder_prompt_char_key() {
let mut app = App::default();
RootFoldersHandler::with(
&Key::Char('h'),
&mut app,
&ActiveRadarrBlock::AddRootFolderPrompt,
&None,
)
.handle();
assert_str_eq!(app.data.radarr_data.edit_path.text, "h");
}
}
#[test]
fn test_root_folders_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if ROOT_FOLDERS_BLOCKS.contains(&active_radarr_block) {
assert!(RootFoldersHandler::accepts(&active_radarr_block));
} else {
assert!(!RootFoldersHandler::accepts(&active_radarr_block));
}
})
}
}
+116
View File
@@ -0,0 +1,116 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::models::Scrollable;
mod system_details_handler;
#[cfg(test)]
#[path = "system_handler_tests.rs"]
mod system_handler_tests;
pub(super) struct SystemHandler<'a, 'b> {
key: &'a Key,
app: &'a mut App<'b>,
active_radarr_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b> {
fn handle(&mut self) {
match self.active_radarr_block {
_ if SystemDetailsHandler::accepts(self.active_radarr_block) => {
SystemDetailsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
.handle()
}
_ => self.handle_key_event(),
}
}
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
SystemDetailsHandler::accepts(active_block) || active_block == &ActiveRadarrBlock::System
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
active_block: &'a ActiveRadarrBlock,
context: &'a Option<ActiveRadarrBlock>,
) -> SystemHandler<'a, 'b> {
SystemHandler {
key,
app,
active_radarr_block: active_block,
context,
}
}
fn get_key(&self) -> &Key {
self.key
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::System {
handle_change_tab_left_right_keys(self.app, self.key);
}
}
fn handle_submit(&mut self) {}
fn handle_esc(&mut self) {
handle_clear_errors(self.app)
}
fn handle_char_key_event(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::System {
let key = self.key;
match self.key {
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.events.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into());
}
_ if *key == DEFAULT_KEYBINDINGS.logs.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemLogs.into());
self
.app
.data
.radarr_data
.log_details
.set_items(self.app.data.radarr_data.logs.items.to_vec());
self.app.data.radarr_data.log_details.scroll_to_bottom();
}
_ if *key == DEFAULT_KEYBINDINGS.tasks.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
}
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
}
_ => (),
}
}
}
}
@@ -18,6 +18,10 @@ pub(super) struct SystemDetailsHandler<'a, 'b> {
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler<'a, 'b> {
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
SYSTEM_DETAILS_BLOCKS.contains(active_block)
}
fn with(
key: &'a Key,
app: &'a mut App<'b>,
@@ -1,12 +1,13 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::system_details_handler::SystemDetailsHandler;
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::{QueueEvent, Task};
@@ -448,4 +449,15 @@ mod tests {
assert!(app.should_refresh);
}
}
#[test]
fn test_system_details_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(SystemDetailsHandler::accepts(&active_radarr_block));
} else {
assert!(!SystemDetailsHandler::accepts(&active_radarr_block));
}
})
}
}
@@ -0,0 +1,211 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::system::SystemHandler;
use crate::handlers::KeyEventHandler;
use crate::test_handler_delegation;
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_system_tab_left() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(5);
SystemHandler::with(
&DEFAULT_KEYBINDINGS.left.key,
&mut app,
&ActiveRadarrBlock::System,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Indexers.into()
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
}
#[test]
fn test_system_tab_right() {
let mut app = App::default();
app.data.radarr_data.main_tabs.set_index(5);
SystemHandler::with(
&DEFAULT_KEYBINDINGS.right.key,
&mut app,
&ActiveRadarrBlock::System,
&None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
&ActiveRadarrBlock::Movies.into()
);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use super::*;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_default_esc() {
let mut app = App::default();
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::System.into());
app.push_navigation_stack(ActiveRadarrBlock::System.into());
SystemHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::System, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::System.into());
assert!(app.error.text.is_empty());
}
}
mod test_handle_key_char {
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::assert_refresh_key;
use crate::models::HorizontallyScrollableText;
use super::*;
#[test]
fn test_update_system_key() {
let mut app = App::default();
SystemHandler::with(
&DEFAULT_KEYBINDINGS.update.key,
&mut app,
&ActiveRadarrBlock::System,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::SystemUpdates.into()
);
}
#[test]
fn test_queued_events_key() {
let mut app = App::default();
SystemHandler::with(
&DEFAULT_KEYBINDINGS.events.key,
&mut app,
&ActiveRadarrBlock::System,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::SystemQueuedEvents.into()
);
}
#[test]
fn test_refresh_system_key() {
assert_refresh_key!(SystemHandler, ActiveRadarrBlock::System);
}
#[test]
fn test_logs_key() {
let mut app = App::default();
app.data.radarr_data.logs.set_items(vec![
HorizontallyScrollableText::from("test 1"),
HorizontallyScrollableText::from("test 2"),
]);
SystemHandler::with(
&DEFAULT_KEYBINDINGS.logs.key,
&mut app,
&ActiveRadarrBlock::System,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::SystemLogs.into()
);
assert_eq!(
app.data.radarr_data.log_details.items,
app.data.radarr_data.logs.items
);
assert_str_eq!(
app.data.radarr_data.log_details.current_selection().text,
"test 2"
);
}
#[test]
fn test_tasks_key() {
let mut app = App::default();
SystemHandler::with(
&DEFAULT_KEYBINDINGS.tasks.key,
&mut app,
&ActiveRadarrBlock::System,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::SystemTasks.into()
);
}
}
#[rstest]
fn test_delegates_system_details_blocks_to_system_details_handler(
#[values(
ActiveRadarrBlock::SystemLogs,
ActiveRadarrBlock::SystemQueuedEvents,
ActiveRadarrBlock::SystemTasks,
ActiveRadarrBlock::SystemTaskStartConfirmPrompt,
ActiveRadarrBlock::SystemUpdates
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
SystemHandler,
ActiveRadarrBlock::System,
active_radarr_block
);
}
#[test]
fn test_system_handler_accepts() {
let mut system_blocks = vec![ActiveRadarrBlock::System];
system_blocks.extend(SYSTEM_DETAILS_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if system_blocks.contains(&active_radarr_block) {
assert!(SystemHandler::accepts(&active_radarr_block));
} else {
assert!(!SystemHandler::accepts(&active_radarr_block));
}
})
}
}
+2 -1
View File
@@ -31,6 +31,7 @@ mod utils;
static HIGHLIGHT_SYMBOL: &str = "=> ";
pub trait DrawUi {
fn accepts(route: Route) -> bool;
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect);
fn draw_context_row<B: Backend>(_f: &mut Frame<'_, B>, _app: &App<'_>, _area: Rect) {}
}
@@ -66,7 +67,7 @@ pub fn ui<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>) {
draw_header_row(f, app, main_chunks[0]);
if let Route::Radarr(_, _) = *app.get_current_route() {
if RadarrUi::accepts(*app.get_current_route()) {
RadarrUi::draw_context_row(f, app, main_chunks[1]);
RadarrUi::draw(f, app, main_chunks[2]);
}
@@ -4,11 +4,11 @@ use tui::text::Text;
use tui::widgets::{Cell, Paragraph, Row, Wrap};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::CollectionMovie;
use crate::models::Route;
use crate::ui::radarr_ui::collections_ui::draw_collections;
use crate::ui::radarr_ui::collections::draw_collections;
use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_top_border_with_title,
line_info_primary, style_default, style_help, style_primary, title_block, title_style,
@@ -17,9 +17,21 @@ use crate::ui::utils::{
use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps};
use crate::utils::convert_runtime;
#[cfg(test)]
#[path = "collection_details_ui_tests.rs"]
mod collection_details_ui_tests;
pub(super) struct CollectionDetailsUi {}
impl DrawUi for CollectionDetailsUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_collection_details_popup =
@@ -50,7 +62,7 @@ impl DrawUi for CollectionDetailsUi {
}
}
pub(super) fn draw_collection_details<B: Backend>(
pub fn draw_collection_details<B: Backend>(
f: &mut Frame<'_, B>,
app: &mut App<'_>,
content_area: Rect,
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS};
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::DrawUi;
#[test]
fn test_collection_details_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(CollectionDetailsUi::accepts(active_radarr_block.into()));
} else {
assert!(!CollectionDetailsUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -0,0 +1,26 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{
ActiveRadarrBlock, COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS,
};
use crate::ui::radarr_ui::collections::CollectionsUi;
use crate::ui::DrawUi;
#[test]
fn test_collections_ui_accepts() {
let mut collections_ui_blocks = Vec::new();
collections_ui_blocks.extend(COLLECTIONS_BLOCKS);
collections_ui_blocks.extend(COLLECTION_DETAILS_BLOCKS);
collections_ui_blocks.extend(EDIT_COLLECTION_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if collections_ui_blocks.contains(&active_radarr_block) {
assert!(CollectionsUi::accepts(active_radarr_block.into()));
} else {
assert!(!CollectionsUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -2,11 +2,11 @@ use tui::backend::Backend;
use tui::layout::{Constraint, Rect};
use tui::Frame;
use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS};
use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS};
use crate::app::App;
use crate::models::Route;
use crate::ui::radarr_ui::collection_details_ui::CollectionDetailsUi;
use crate::ui::radarr_ui::collections_ui::draw_collections;
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::radarr_ui::collections::draw_collections;
use crate::ui::radarr_ui::{
draw_select_minimum_availability_popup, draw_select_quality_profile_popup,
};
@@ -19,9 +19,21 @@ use crate::ui::{
draw_text_box_with_label, DrawUi,
};
#[cfg(test)]
#[path = "edit_collection_ui_tests.rs"]
mod edit_collection_ui_tests;
pub(super) struct EditCollectionUi {}
impl DrawUi for EditCollectionUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_edit_collection_prompt =
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi;
use crate::ui::DrawUi;
#[test]
fn test_edit_collection_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) {
assert!(EditCollectionUi::accepts(active_radarr_block.into()));
} else {
assert!(!EditCollectionUi::accepts(active_radarr_block.into()));
}
})
}
}
@@ -3,50 +3,80 @@ use tui::layout::{Constraint, Rect};
use tui::widgets::{Cell, Row};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
pub(super) use collection_details_ui::draw_collection_details;
use crate::app::radarr::{ActiveRadarrBlock, COLLECTIONS_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::Collection;
use crate::models::Route;
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi;
use crate::ui::radarr_ui::{draw_filter_box, draw_search_box};
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary};
use crate::ui::{
draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps,
};
mod collection_details_ui;
#[cfg(test)]
#[path = "collections_ui_tests.rs"]
mod collections_ui_tests;
mod edit_collection_ui;
pub(super) struct CollectionsUi {}
impl DrawUi for CollectionsUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return CollectionDetailsUi::accepts(route)
|| EditCollectionUi::accepts(route)
|| COLLECTIONS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect),
ActiveRadarrBlock::SearchCollection => draw_popup_over(
f,
app,
content_rect,
draw_collections,
draw_search_box,
30,
11,
),
ActiveRadarrBlock::FilterCollections => draw_popup_over(
f,
app,
content_rect,
draw_collections,
draw_filter_box,
30,
11,
),
ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_collections,
draw_update_all_collections_prompt,
),
_ => (),
let route = *app.get_current_route();
let mut collections_ui_matcher = |active_radarr_block| match active_radarr_block {
ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect),
ActiveRadarrBlock::SearchCollection => draw_popup_over(
f,
app,
content_rect,
draw_collections,
draw_search_box,
30,
11,
),
ActiveRadarrBlock::FilterCollections => draw_popup_over(
f,
app,
content_rect,
draw_collections,
draw_filter_box,
30,
11,
),
ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_collections,
draw_update_all_collections_prompt,
),
_ => (),
};
match route {
_ if CollectionDetailsUi::accepts(route) => CollectionDetailsUi::draw(f, app, content_rect),
_ if EditCollectionUi::accepts(route) => EditCollectionUi::draw(f, app, content_rect),
Route::Radarr(active_radarr_block, _)
if COLLECTIONS_BLOCKS.contains(&active_radarr_block) =>
{
collections_ui_matcher(active_radarr_block)
}
_ => (),
}
}
}
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::ui::radarr_ui::downloads::DownloadsUi;
use crate::ui::DrawUi;
#[test]
fn test_downloads_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if DOWNLOADS_BLOCKS.contains(&active_radarr_block) {
assert!(DownloadsUi::accepts(active_radarr_block.into()));
} else {
assert!(!DownloadsUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -3,7 +3,7 @@ use tui::layout::{Constraint, Rect};
use tui::widgets::{Cell, Row};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::DownloadRecord;
use crate::models::{HorizontallyScrollableText, Route};
@@ -11,9 +11,21 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
use crate::utils::convert_to_gb;
#[cfg(test)]
#[path = "downloads_ui_tests.rs"]
mod downloads_ui_tests;
pub(super) struct DownloadsUi {}
impl DrawUi for DownloadsUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return DOWNLOADS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
@@ -0,0 +1,26 @@
use tui::backend::Backend;
use tui::layout::Rect;
use tui::Frame;
use crate::app::radarr::INDEXER_SETTINGS_BLOCKS;
use crate::app::App;
use crate::models::Route;
use crate::ui::DrawUi;
#[cfg(test)]
#[path = "indexer_settings_ui_tests.rs"]
mod indexer_settings_ui_tests;
pub(super) struct IndexerSettingsUi {}
impl DrawUi for IndexerSettingsUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(_f: &mut Frame<'_, B>, _app: &mut App<'_>, _content_rect: Rect) {}
}
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS};
use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::DrawUi;
#[test]
fn test_indexer_settings_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block) {
assert!(IndexerSettingsUi::accepts(active_radarr_block.into()));
} else {
assert!(!IndexerSettingsUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -0,0 +1,23 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS};
use crate::ui::radarr_ui::indexers::IndexersUi;
use crate::ui::DrawUi;
#[test]
fn test_indexers_ui_accepts() {
let mut indexers_blocks = Vec::new();
indexers_blocks.extend(INDEXERS_BLOCKS);
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if indexers_blocks.contains(&active_radarr_block) {
assert!(IndexersUi::accepts(active_radarr_block.into()));
} else {
assert!(!IndexersUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -4,29 +4,51 @@ use tui::text::Text;
use tui::widgets::{Cell, Row};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, INDEXERS_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::Indexer;
use crate::models::Route;
use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success};
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
mod indexer_settings_ui;
#[cfg(test)]
#[path = "indexers_ui_tests.rs"]
mod indexers_ui_tests;
pub(super) struct IndexersUi {}
impl DrawUi for IndexersUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return IndexerSettingsUi::accepts(route) || INDEXERS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect),
ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_indexers,
draw_delete_indexer_prompt,
),
_ => (),
let route = *app.get_current_route();
let mut indexers_matchers = |active_radarr_block| match active_radarr_block {
ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect),
ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_indexers,
draw_delete_indexer_prompt,
),
_ => (),
};
match route {
_ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect),
Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => {
indexers_matchers(active_radarr_block)
}
_ => (),
}
}
}
@@ -7,9 +7,8 @@ use tui::Frame;
use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS};
use crate::models::radarr_models::AddMovieSearchResult;
use crate::models::Route;
use crate::ui::radarr_ui::collection_details_ui::draw_collection_details;
use crate::ui::radarr_ui::collections_ui::draw_collections;
use crate::ui::radarr_ui::library_ui::draw_library;
use crate::ui::radarr_ui::collections::{draw_collection_details, draw_collections};
use crate::ui::radarr_ui::library::draw_library;
use crate::ui::radarr_ui::{
draw_select_minimum_availability_popup, draw_select_quality_profile_popup,
draw_select_root_folder_popup,
@@ -27,9 +26,21 @@ use crate::ui::{
use crate::utils::convert_runtime;
use crate::App;
pub(super) struct AddMoviesUi {}
#[cfg(test)]
#[path = "add_movie_ui_tests.rs"]
mod add_movie_ui_tests;
pub(super) struct AddMovieUi {}
impl DrawUi for AddMovieUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return ADD_MOVIE_BLOCKS.contains(&active_radarr_block);
}
false
}
impl DrawUi for AddMoviesUi {
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_add_movie_search_popup =
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS};
use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi;
use crate::ui::DrawUi;
#[test]
fn test_add_movie_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(AddMovieUi::accepts(active_radarr_block.into()));
} else {
assert!(!AddMovieUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -2,15 +2,27 @@ use tui::backend::Backend;
use tui::layout::Rect;
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::app::App;
use crate::models::Route;
use crate::ui::radarr_ui::library_ui::draw_library;
use crate::ui::radarr_ui::library::draw_library;
use crate::ui::{draw_prompt_box_with_checkboxes, draw_prompt_popup_over, DrawUi};
#[cfg(test)]
#[path = "delete_movie_ui_tests.rs"]
mod delete_movie_ui_tests;
pub(super) struct DeleteMovieUi {}
impl DrawUi for DeleteMovieUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return DELETE_MOVIE_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if matches!(
*app.get_current_route(),
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi;
use crate::ui::DrawUi;
#[test]
fn test_delete_movie_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(DeleteMovieUi::accepts(active_radarr_block.into()));
} else {
assert!(!DeleteMovieUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -2,11 +2,11 @@ use tui::backend::Backend;
use tui::layout::{Constraint, Rect};
use tui::Frame;
use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS, MOVIE_DETAILS_BLOCKS};
use crate::app::App;
use crate::models::Route;
use crate::ui::radarr_ui::library_ui::draw_library;
use crate::ui::radarr_ui::movie_details_ui::MovieDetailsUi;
use crate::ui::radarr_ui::library::draw_library;
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::radarr_ui::{
draw_select_minimum_availability_popup, draw_select_quality_profile_popup,
};
@@ -19,9 +19,21 @@ use crate::ui::{
draw_text_box_with_label, DrawUi,
};
#[cfg(test)]
#[path = "edit_movie_ui_tests.rs"]
mod edit_movie_ui_tests;
pub(super) struct EditMovieUi {}
impl DrawUi for EditMovieUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return EDIT_MOVIE_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_edit_movie_prompt =
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi;
use crate::ui::DrawUi;
#[test]
fn test_edit_movie_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) {
assert!(EditMovieUi::accepts(active_radarr_block.into()));
} else {
assert!(!EditMovieUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -0,0 +1,29 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, DELETE_MOVIE_BLOCKS, EDIT_MOVIE_BLOCKS, LIBRARY_BLOCKS,
MOVIE_DETAILS_BLOCKS,
};
use crate::ui::radarr_ui::library::LibraryUi;
use crate::ui::DrawUi;
#[test]
fn test_library_ui_accepts() {
let mut library_ui_blocks = Vec::new();
library_ui_blocks.extend(LIBRARY_BLOCKS);
library_ui_blocks.extend(MOVIE_DETAILS_BLOCKS);
library_ui_blocks.extend(ADD_MOVIE_BLOCKS);
library_ui_blocks.extend(EDIT_MOVIE_BLOCKS);
library_ui_blocks.extend(DELETE_MOVIE_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if library_ui_blocks.contains(&active_radarr_block) {
assert!(LibraryUi::accepts(active_radarr_block.into()));
} else {
assert!(!LibraryUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -3,10 +3,14 @@ use tui::layout::{Constraint, Rect};
use tui::widgets::{Cell, Row};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, LIBRARY_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::Movie;
use crate::models::Route;
use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi;
use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi;
use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi;
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::radarr_ui::{determine_row_style, draw_filter_box, draw_search_box};
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::{
@@ -14,28 +18,60 @@ use crate::ui::{
};
use crate::utils::{convert_runtime, convert_to_gb};
mod add_movie_ui;
mod delete_movie_ui;
mod edit_movie_ui;
mod movie_details_ui;
#[cfg(test)]
#[path = "library_ui_tests.rs"]
mod library_ui_tests;
pub(super) struct LibraryUi {}
impl DrawUi for LibraryUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return MovieDetailsUi::accepts(route)
|| AddMovieUi::accepts(route)
|| EditMovieUi::accepts(route)
|| DeleteMovieUi::accepts(route)
|| LIBRARY_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
match active_radarr_block {
ActiveRadarrBlock::Movies => draw_library(f, app, content_rect),
ActiveRadarrBlock::SearchMovie => {
draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 11)
}
ActiveRadarrBlock::FilterMovies => {
draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 11)
}
ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_library,
draw_update_all_movies_prompt,
),
_ => (),
let route = *app.get_current_route();
let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block
{
ActiveRadarrBlock::Movies => draw_library(f, app, content_rect),
ActiveRadarrBlock::SearchMovie => {
draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 11)
}
ActiveRadarrBlock::FilterMovies => {
draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 11)
}
ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_library,
draw_update_all_movies_prompt,
),
_ => (),
};
match route {
_ if MovieDetailsUi::accepts(route) => MovieDetailsUi::draw(f, app, content_rect),
_ if AddMovieUi::accepts(route) => AddMovieUi::draw(f, app, content_rect),
_ if EditMovieUi::accepts(route) => EditMovieUi::draw(f, app, content_rect),
_ if DeleteMovieUi::accepts(route) => DeleteMovieUi::draw(f, app, content_rect),
Route::Radarr(active_radarr_block, _) if LIBRARY_BLOCKS.contains(&active_radarr_block) => {
library_ui_matchers(active_radarr_block)
}
_ => (),
}
}
}
@@ -7,11 +7,11 @@ use tui::text::{Line, Span, Text};
use tui::widgets::{Cell, ListItem, Paragraph, Row, Wrap};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::{Credit, MovieHistoryItem, Release, ReleaseField};
use crate::models::Route;
use crate::ui::radarr_ui::library_ui::draw_library;
use crate::ui::radarr_ui::library::draw_library;
use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border,
line_info_default, style_awaiting_import, style_bold, style_default, style_failure,
@@ -24,9 +24,21 @@ use crate::ui::{
};
use crate::utils::convert_to_gb;
#[cfg(test)]
#[path = "movie_details_ui_tests.rs"]
mod movie_details_ui_tests;
pub(super) struct MovieDetailsUi {}
impl DrawUi for MovieDetailsUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_movie_info_popup = |f: &mut Frame<'_, B>, app: &mut App<'_>, popup_area: Rect| {
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::DrawUi;
#[test]
fn test_movie_details_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(MovieDetailsUi::accepts(active_radarr_block.into()));
} else {
assert!(!MovieDetailsUi::accepts(active_radarr_block.into()));
}
});
}
}
+30 -72
View File
@@ -9,11 +9,7 @@ use tui::widgets::ListItem;
use tui::widgets::Paragraph;
use tui::Frame;
use crate::app::radarr::{
ActiveRadarrBlock, RadarrData, ADD_MOVIE_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS,
EDIT_COLLECTION_BLOCKS, EDIT_MOVIE_BLOCKS, FILTER_BLOCKS, INDEXER_BLOCKS, MOVIE_DETAILS_BLOCKS,
SEARCH_BLOCKS, SYSTEM_DETAILS_BLOCKS,
};
use crate::app::radarr::{ActiveRadarrBlock, RadarrData, FILTER_BLOCKS, SEARCH_BLOCKS};
use crate::app::App;
use crate::logos::RADARR_LOGO;
use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder};
@@ -21,15 +17,12 @@ use crate::models::Route;
use crate::ui::draw_selectable_list;
use crate::ui::draw_tabs;
use crate::ui::loading;
use crate::ui::radarr_ui::indexers_ui::IndexersUi;
use crate::ui::radarr_ui::system_details_ui::SystemDetailsUi;
use crate::ui::radarr_ui::system_ui::SystemUi;
use crate::ui::radarr_ui::{
add_movie_ui::AddMoviesUi, collection_details_ui::CollectionDetailsUi,
collections_ui::CollectionsUi, delete_movie_ui::DeleteMovieUi, downloads_ui::DownloadsUi,
edit_collection_ui::EditCollectionUi, edit_movie_ui::EditMovieUi, library_ui::LibraryUi,
movie_details_ui::MovieDetailsUi, root_folders_ui::RootFoldersUi,
};
use crate::ui::radarr_ui::collections::CollectionsUi;
use crate::ui::radarr_ui::downloads::DownloadsUi;
use crate::ui::radarr_ui::indexers::IndexersUi;
use crate::ui::radarr_ui::library::LibraryUi;
use crate::ui::radarr_ui::root_folders::RootFoldersUi;
use crate::ui::radarr_ui::system::SystemUi;
use crate::ui::utils::{
borderless_block, horizontal_chunks, layout_block, line_gauge_with_label, line_gauge_with_title,
show_cursor, style_awaiting_import, style_bold, style_default, style_failure, style_success,
@@ -38,72 +31,37 @@ use crate::ui::utils::{
use crate::ui::DrawUi;
use crate::utils::convert_to_gb;
mod add_movie_ui;
mod collection_details_ui;
mod collections_ui;
mod delete_movie_ui;
mod downloads_ui;
mod edit_collection_ui;
mod edit_movie_ui;
mod indexers_ui;
mod library_ui;
mod movie_details_ui;
mod collections;
mod downloads;
mod indexers;
mod library;
mod radarr_ui_utils;
mod root_folders_ui;
mod system_details_ui;
mod system_ui;
mod root_folders;
mod system;
#[cfg(test)]
#[path = "radarr_ui_tests.rs"]
mod radarr_ui_tests;
pub(super) struct RadarrUi {}
impl DrawUi for RadarrUi {
fn accepts(route: Route) -> bool {
matches!(route, Route::Radarr(_, _))
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) {
let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs);
let route = *app.get_current_route();
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
ActiveRadarrBlock::Movies
| ActiveRadarrBlock::SearchMovie
| ActiveRadarrBlock::FilterMovies
| ActiveRadarrBlock::UpdateAllMoviesPrompt => LibraryUi::draw(f, app, content_rect),
ActiveRadarrBlock::Collections
| ActiveRadarrBlock::SearchCollection
| ActiveRadarrBlock::FilterCollections
| ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
CollectionsUi::draw(f, app, content_rect)
}
ActiveRadarrBlock::Downloads
| ActiveRadarrBlock::DeleteDownloadPrompt
| ActiveRadarrBlock::UpdateDownloadsPrompt => DownloadsUi::draw(f, app, content_rect),
_ if INDEXER_BLOCKS.contains(&active_radarr_block) => {
IndexersUi::draw(f, app, content_rect)
}
ActiveRadarrBlock::RootFolders
| ActiveRadarrBlock::AddRootFolderPrompt
| ActiveRadarrBlock::DeleteRootFolderPrompt => RootFoldersUi::draw(f, app, content_rect),
ActiveRadarrBlock::System => SystemUi::draw(f, app, content_rect),
_ if SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block) => {
SystemDetailsUi::draw(f, app, content_rect)
}
_ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => {
MovieDetailsUi::draw(f, app, content_rect)
}
_ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => {
AddMoviesUi::draw(f, app, content_rect)
}
_ if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) => {
CollectionDetailsUi::draw(f, app, content_rect)
}
_ if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) => {
EditMovieUi::draw(f, app, content_rect)
}
_ if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) => {
EditCollectionUi::draw(f, app, content_rect)
}
_ if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) => {
DeleteMovieUi::draw(f, app, content_rect)
}
_ => (),
}
match route {
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_rect),
_ if CollectionsUi::accepts(route) => CollectionsUi::draw(f, app, content_rect),
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_rect),
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_rect),
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_rect),
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_rect),
_ => (),
}
}
+15
View File
@@ -0,0 +1,15 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::ActiveRadarrBlock;
use crate::ui::radarr_ui::RadarrUi;
use crate::ui::DrawUi;
#[test]
fn test_radarr_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
assert!(RadarrUi::accepts(active_radarr_block.into()));
});
}
}
@@ -3,7 +3,7 @@ use tui::layout::{Alignment, Constraint, Rect};
use tui::widgets::{Cell, Paragraph, Row};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
use crate::app::App;
use crate::models::radarr_models::RootFolder;
use crate::models::Route;
@@ -16,9 +16,21 @@ use crate::ui::{
};
use crate::utils::convert_to_gb;
#[cfg(test)]
#[path = "root_folders_ui_tests.rs"]
mod root_folders_ui_tests;
pub(super) struct RootFoldersUi {}
impl DrawUi for RootFoldersUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return ROOT_FOLDERS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
use crate::ui::radarr_ui::root_folders::RootFoldersUi;
use crate::ui::DrawUi;
#[test]
fn test_root_folders_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if ROOT_FOLDERS_BLOCKS.contains(&active_radarr_block) {
assert!(RootFoldersUi::accepts(active_radarr_block.into()));
} else {
assert!(!RootFoldersUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -1,7 +1,21 @@
use std::ops::Sub;
use chrono::Utc;
use tui::layout::Alignment;
use tui::text::{Span, Text};
use tui::widgets::{Cell, Paragraph, Row};
use tui::{
backend::Backend,
layout::{Constraint, Rect},
widgets::ListItem,
Frame,
};
use crate::models::radarr_models::Task;
use crate::ui::radarr_ui::radarr_ui_utils::{
convert_to_minutes_hours_days, determine_log_style_by_level,
};
use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi;
use crate::ui::utils::{layout_block_top_border, style_help, style_primary};
use crate::ui::{draw_table, ListProps, TableProps};
use crate::{
@@ -13,17 +27,12 @@ use crate::{
DrawUi,
},
};
use chrono::Utc;
use std::ops::Sub;
use tui::layout::Alignment;
use tui::text::{Span, Text};
use tui::widgets::{Cell, Paragraph, Row};
use tui::{
backend::Backend,
layout::{Constraint, Rect},
widgets::ListItem,
Frame,
};
mod system_details_ui;
#[cfg(test)]
#[path = "system_ui_tests.rs"]
mod system_ui_tests;
pub(super) const TASK_TABLE_HEADERS: [&str; 5] = [
"Name",
@@ -44,12 +53,23 @@ pub(super) const TASK_TABLE_CONSTRAINTS: [Constraint; 5] = [
pub(super) struct SystemUi {}
impl DrawUi for SystemUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return SystemDetailsUi::accepts(route) || active_radarr_block == ActiveRadarrBlock::System;
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if matches!(
*app.get_current_route(),
Route::Radarr(ActiveRadarrBlock::System, _)
) {
draw_system_ui_layout(f, app, content_rect)
let route = *app.get_current_route();
match route {
_ if SystemDetailsUi::accepts(route) => SystemDetailsUi::draw(f, app, content_rect),
_ if matches!(route, Route::Radarr(ActiveRadarrBlock::System, _)) => {
draw_system_ui_layout(f, app, content_rect)
}
_ => (),
}
}
}
@@ -4,11 +4,11 @@ use tui::text::{Span, Text};
use tui::widgets::{Cell, ListItem, Paragraph, Row};
use tui::Frame;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::app::App;
use crate::models::Route;
use crate::ui::radarr_ui::radarr_ui_utils::determine_log_style_by_level;
use crate::ui::radarr_ui::system_ui::{
use crate::ui::radarr_ui::system::{
draw_queued_events, draw_system_ui_layout, extract_task_props, TASK_TABLE_CONSTRAINTS,
TASK_TABLE_HEADERS,
};
@@ -18,9 +18,21 @@ use crate::ui::{
draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, ListProps, TableProps,
};
#[cfg(test)]
#[path = "system_details_ui_tests.rs"]
mod system_details_ui_tests;
pub(super) struct SystemDetailsUi {}
impl DrawUi for SystemDetailsUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi;
use crate::ui::DrawUi;
#[test]
fn test_system_details_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if SYSTEM_DETAILS_BLOCKS.contains(&active_radarr_block) {
assert!(SystemDetailsUi::accepts(active_radarr_block.into()));
} else {
assert!(!SystemDetailsUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -0,0 +1,23 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::radarr::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::ui::radarr_ui::system::SystemUi;
use crate::ui::DrawUi;
#[test]
fn test_system_ui_accepts() {
let mut system_ui_blocks = Vec::new();
system_ui_blocks.push(ActiveRadarrBlock::System);
system_ui_blocks.extend(SYSTEM_DETAILS_BLOCKS);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if system_ui_blocks.contains(&active_radarr_block) {
assert!(SystemUi::accepts(active_radarr_block.into()));
} else {
assert!(!SystemUi::accepts(active_radarr_block.into()));
}
});
}
}