diff --git a/src/handlers/readarr_handlers/mod.rs b/src/handlers/readarr_handlers/mod.rs index 50c47a3c..759f172d 100644 --- a/src/handlers/readarr_handlers/mod.rs +++ b/src/handlers/readarr_handlers/mod.rs @@ -4,6 +4,7 @@ use crate::handlers::readarr_handlers::downloads::DownloadsHandler; use crate::handlers::readarr_handlers::history::HistoryHandler; use crate::handlers::readarr_handlers::library::LibraryHandler; use crate::handlers::readarr_handlers::root_folders::RootFoldersHandler; +use crate::handlers::readarr_handlers::system::SystemHandler; use crate::models::Route; use crate::{ app::App, event::Key, matches_key, @@ -15,6 +16,7 @@ mod downloads; mod history; mod library; mod root_folders; +mod system; #[cfg(test)] #[path = "readarr_handler_tests.rs"] @@ -46,6 +48,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveReadarrBlock> for ReadarrHandler<'a, RootFoldersHandler::new(self.key, self.app, self.active_readarr_block, self.context) .handle(); } + _ if SystemHandler::accepts(self.active_readarr_block) => { + SystemHandler::new(self.key, self.app, self.active_readarr_block, self.context).handle(); + } _ => self.handle_key_event(), } } diff --git a/src/handlers/readarr_handlers/readarr_handler_tests.rs b/src/handlers/readarr_handlers/readarr_handler_tests.rs index 819b66f4..00504374 100644 --- a/src/handlers/readarr_handlers/readarr_handler_tests.rs +++ b/src/handlers/readarr_handlers/readarr_handler_tests.rs @@ -246,4 +246,23 @@ mod tests { active_readarr_block ); } + + #[rstest] + fn test_delegates_system_blocks_to_system_handler( + #[values( + ActiveReadarrBlock::System, + ActiveReadarrBlock::SystemLogs, + ActiveReadarrBlock::SystemQueuedEvents, + ActiveReadarrBlock::SystemTasks, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + ActiveReadarrBlock::SystemUpdates + )] + active_readarr_block: ActiveReadarrBlock, + ) { + test_handler_delegation!( + ReadarrHandler, + ActiveReadarrBlock::System, + active_readarr_block + ); + } } diff --git a/src/handlers/readarr_handlers/system/mod.rs b/src/handlers/readarr_handlers/system/mod.rs new file mode 100644 index 00000000..caffa715 --- /dev/null +++ b/src/handlers/readarr_handlers/system/mod.rs @@ -0,0 +1,135 @@ +use crate::app::App; +use crate::event::Key; +use crate::handlers::readarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::readarr_handlers::system::system_details_handler::SystemDetailsHandler; +use crate::handlers::{KeyEventHandler, handle_clear_errors}; +use crate::matches_key; +use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock; +use crate::models::{Route, Scrollable}; + +mod system_details_handler; + +#[cfg(test)] +#[path = "system_handler_tests.rs"] +mod system_handler_tests; + +pub(super) struct SystemHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_readarr_block: ActiveReadarrBlock, + context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveReadarrBlock> for SystemHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_readarr_block { + _ if SystemDetailsHandler::accepts(self.active_readarr_block) => { + SystemDetailsHandler::new(self.key, self.app, self.active_readarr_block, self.context) + .handle() + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: ActiveReadarrBlock) -> bool { + SystemDetailsHandler::accepts(active_block) || active_block == ActiveReadarrBlock::System + } + + fn ignore_special_keys(&self) -> bool { + self.app.ignore_special_keys_for_textbox_input + } + + fn new( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveReadarrBlock, + context: Option, + ) -> SystemHandler<'a, 'b> { + SystemHandler { + key, + app, + active_readarr_block: active_block, + context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading + && !self.app.data.readarr_data.logs.is_empty() + && !self.app.data.readarr_data.queued_events.is_empty() + && !self.app.data.readarr_data.tasks.is_empty() + } + + 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_readarr_block == ActiveReadarrBlock::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_readarr_block == ActiveReadarrBlock::System { + let key = self.key; + match self.key { + _ if matches_key!(refresh, key) => { + self.app.should_refresh = true; + } + _ if matches_key!(events, key) => { + self + .app + .push_navigation_stack(ActiveReadarrBlock::SystemQueuedEvents.into()); + } + _ if matches_key!(logs, key) => { + self + .app + .push_navigation_stack(ActiveReadarrBlock::SystemLogs.into()); + self + .app + .data + .readarr_data + .log_details + .set_items(self.app.data.readarr_data.logs.items.to_vec()); + self.app.data.readarr_data.log_details.scroll_to_bottom(); + } + _ if matches_key!(tasks, key) => { + self + .app + .push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + } + _ if matches_key!(update, key) => { + self + .app + .push_navigation_stack(ActiveReadarrBlock::SystemUpdates.into()); + } + _ => (), + } + } + } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> Route { + self.app.get_current_route() + } +} diff --git a/src/handlers/readarr_handlers/system/system_details_handler.rs b/src/handlers/readarr_handlers/system/system_details_handler.rs new file mode 100644 index 00000000..432a9bf1 --- /dev/null +++ b/src/handlers/readarr_handlers/system/system_details_handler.rs @@ -0,0 +1,211 @@ +use crate::app::App; +use crate::event::Key; +use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; +use crate::matches_key; +use crate::models::readarr_models::ReadarrTaskName; +use crate::models::servarr_data::readarr::readarr_data::{ + ActiveReadarrBlock, SYSTEM_DETAILS_BLOCKS, +}; +use crate::models::stateful_list::StatefulList; +use crate::models::{Route, Scrollable}; +use crate::network::readarr_network::ReadarrEvent; + +#[cfg(test)] +#[path = "system_details_handler_tests.rs"] +mod system_details_handler_tests; + +pub(super) struct SystemDetailsHandler<'a, 'b> { + key: Key, + app: &'a mut App<'b>, + active_readarr_block: ActiveReadarrBlock, + _context: Option, +} + +impl SystemDetailsHandler<'_, '_> { + fn extract_task_name(&self) -> ReadarrTaskName { + self + .app + .data + .readarr_data + .tasks + .current_selection() + .task_name + } +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveReadarrBlock> for SystemDetailsHandler<'a, 'b> { + fn accepts(active_block: ActiveReadarrBlock) -> bool { + SYSTEM_DETAILS_BLOCKS.contains(&active_block) + } + + fn ignore_special_keys(&self) -> bool { + self.app.ignore_special_keys_for_textbox_input + } + + fn new( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveReadarrBlock, + context: Option, + ) -> SystemDetailsHandler<'a, 'b> { + SystemDetailsHandler { + key, + app, + active_readarr_block: active_block, + _context: context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading + && (!self.app.data.readarr_data.log_details.is_empty() + || !self.app.data.readarr_data.tasks.is_empty() + || !self.app.data.readarr_data.updates.is_empty()) + } + + fn handle_scroll_up(&mut self) { + match self.active_readarr_block { + ActiveReadarrBlock::SystemLogs => self.app.data.readarr_data.log_details.scroll_up(), + ActiveReadarrBlock::SystemTasks => self.app.data.readarr_data.tasks.scroll_up(), + ActiveReadarrBlock::SystemUpdates => self.app.data.readarr_data.updates.scroll_up(), + ActiveReadarrBlock::SystemQueuedEvents => { + self.app.data.readarr_data.queued_events.scroll_up() + } + _ => (), + } + } + + fn handle_scroll_down(&mut self) { + match self.active_readarr_block { + ActiveReadarrBlock::SystemLogs => self.app.data.readarr_data.log_details.scroll_down(), + ActiveReadarrBlock::SystemTasks => self.app.data.readarr_data.tasks.scroll_down(), + ActiveReadarrBlock::SystemUpdates => self.app.data.readarr_data.updates.scroll_down(), + ActiveReadarrBlock::SystemQueuedEvents => { + self.app.data.readarr_data.queued_events.scroll_down() + } + _ => (), + } + } + + fn handle_home(&mut self) { + match self.active_readarr_block { + ActiveReadarrBlock::SystemLogs => self.app.data.readarr_data.log_details.scroll_to_top(), + ActiveReadarrBlock::SystemTasks => self.app.data.readarr_data.tasks.scroll_to_top(), + ActiveReadarrBlock::SystemUpdates => self.app.data.readarr_data.updates.scroll_to_top(), + ActiveReadarrBlock::SystemQueuedEvents => { + self.app.data.readarr_data.queued_events.scroll_to_top() + } + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_readarr_block { + ActiveReadarrBlock::SystemLogs => self.app.data.readarr_data.log_details.scroll_to_bottom(), + ActiveReadarrBlock::SystemTasks => self.app.data.readarr_data.tasks.scroll_to_bottom(), + ActiveReadarrBlock::SystemUpdates => self.app.data.readarr_data.updates.scroll_to_bottom(), + ActiveReadarrBlock::SystemQueuedEvents => { + self.app.data.readarr_data.queued_events.scroll_to_bottom() + } + _ => (), + } + } + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { + let key = self.key; + + match self.active_readarr_block { + ActiveReadarrBlock::SystemLogs => match self.key { + _ if matches_key!(left, key) => { + self + .app + .data + .readarr_data + .log_details + .items + .iter() + .for_each(|log| log.scroll_right()); + } + _ if matches_key!(right, key) => { + self + .app + .data + .readarr_data + .log_details + .items + .iter() + .for_each(|log| log.scroll_left()); + } + _ => (), + }, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt => handle_prompt_toggle(self.app, self.key), + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_readarr_block { + ActiveReadarrBlock::SystemTasks => { + self + .app + .push_navigation_stack(ActiveReadarrBlock::SystemTaskStartConfirmPrompt.into()); + } + ActiveReadarrBlock::SystemTaskStartConfirmPrompt => { + if self.app.data.readarr_data.prompt_confirm { + self.app.data.readarr_data.prompt_confirm_action = + Some(ReadarrEvent::StartTask(self.extract_task_name())); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_readarr_block { + ActiveReadarrBlock::SystemLogs => { + self.app.data.readarr_data.log_details = StatefulList::default(); + self.app.pop_navigation_stack() + } + ActiveReadarrBlock::SystemQueuedEvents + | ActiveReadarrBlock::SystemTasks + | ActiveReadarrBlock::SystemUpdates => self.app.pop_navigation_stack(), + ActiveReadarrBlock::SystemTaskStartConfirmPrompt => { + self.app.pop_navigation_stack(); + self.app.data.readarr_data.prompt_confirm = false; + } + _ => (), + } + } + + fn handle_char_key_event(&mut self) { + if SYSTEM_DETAILS_BLOCKS.contains(&self.active_readarr_block) && matches_key!(refresh, self.key) + { + self.app.should_refresh = true; + } + + if self.active_readarr_block == ActiveReadarrBlock::SystemTaskStartConfirmPrompt + && matches_key!(confirm, self.key) + { + self.app.data.readarr_data.prompt_confirm = true; + self.app.data.readarr_data.prompt_confirm_action = + Some(ReadarrEvent::StartTask(self.extract_task_name())); + self.app.pop_navigation_stack(); + } + } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> Route { + self.app.get_current_route() + } +} diff --git a/src/handlers/readarr_handlers/system/system_details_handler_tests.rs b/src/handlers/readarr_handlers/system/system_details_handler_tests.rs new file mode 100644 index 00000000..27af3c9d --- /dev/null +++ b/src/handlers/readarr_handlers/system/system_details_handler_tests.rs @@ -0,0 +1,1151 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::App; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::assert_navigation_pushed; + use crate::event::Key; + use crate::handlers::KeyEventHandler; + use crate::handlers::readarr_handlers::system::system_details_handler::SystemDetailsHandler; + use crate::models::readarr_models::{ReadarrTask, ReadarrTaskName}; + use crate::models::servarr_data::readarr::readarr_data::{ + ActiveReadarrBlock, SYSTEM_DETAILS_BLOCKS, + }; + use crate::models::servarr_models::QueueEvent; + use crate::models::{HorizontallyScrollableText, ScrollableText}; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::models::HorizontallyScrollableText; + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_log_details_scroll, + SystemDetailsHandler, + readarr_data, + log_details, + simple_stateful_iterable_vec!(HorizontallyScrollableText, String, text), + ActiveReadarrBlock::SystemLogs, + None, + text + ); + + #[rstest] + fn test_log_details_scroll_no_op_when_not_ready( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app + .data + .readarr_data + .log_details + .set_items(simple_stateful_iterable_vec!( + HorizontallyScrollableText, + String, + text + )); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemLogs, None).handle(); + + assert_str_eq!( + app.data.readarr_data.log_details.current_selection().text, + "Test 1" + ); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemLogs, None).handle(); + + assert_str_eq!( + app.data.readarr_data.log_details.current_selection().text, + "Test 1" + ); + } + + #[rstest] + fn test_tasks_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.tasks.set_items(tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Rss sync task" + ); + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + } + + #[rstest] + fn test_tasks_scroll_no_op_when_not_ready( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.tasks.set_items(tasks_vec()); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + } + + #[rstest] + fn test_queued_events_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.tasks.set_items(tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event two" + ); + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + } + + #[rstest] + fn test_queued_events_scroll_no_op_when_not_ready( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.tasks.set_items(tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + + SystemDetailsHandler::new(key, &mut app, ActiveReadarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + } + + #[test] + fn test_system_updates_scroll() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.updates = updates_text(); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.up.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 0); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.down.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 1); + } + + #[test] + fn test_system_updates_scroll_no_op_when_not_ready() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.updates = updates_text(); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.up.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 0); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.down.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 0); + } + } + + mod test_handle_home_end { + use crate::models::HorizontallyScrollableText; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + use pretty_assertions::assert_eq; + + test_iterable_home_and_end!( + test_log_details_home_end, + SystemDetailsHandler, + readarr_data, + log_details, + extended_stateful_iterable_vec!(HorizontallyScrollableText, String, text), + ActiveReadarrBlock::SystemLogs, + None, + text + ); + + #[test] + fn test_log_details_home_end_no_op_when_not_ready() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app + .data + .readarr_data + .log_details + .set_items(extended_stateful_iterable_vec!( + HorizontallyScrollableText, + String, + text + )); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemLogs, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.log_details.current_selection().text, + "Test 1" + ); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemLogs, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.log_details.current_selection().text, + "Test 1" + ); + } + + #[test] + fn test_tasks_home_end() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.tasks.set_items(extended_tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(extended_queued_events_vec()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Housekeeping task" + ); + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + } + + #[test] + fn test_tasks_home_end_no_op_when_not_ready() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.tasks.set_items(extended_tasks_vec()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + } + + #[test] + fn test_queued_events_home_end() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.tasks.set_items(extended_tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(extended_queued_events_vec()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event three" + ); + assert_str_eq!( + app.data.readarr_data.tasks.current_selection().name, + "Backup task" + ); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + } + + #[test] + fn test_queued_events_home_end_no_op_when_not_ready() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.tasks.set_items(extended_tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(extended_queued_events_vec()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.readarr_data.queued_events.current_selection().name, + "queued event one" + ); + } + + #[test] + fn test_system_updates_home_end() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.updates = updates_text(); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 1); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 0); + } + + #[test] + fn test_system_updates_home_end_no_op_when_not_ready() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.updates = updates_text(); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 0); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.updates.offset, 0); + } + } + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[test] + fn test_handle_log_details_left_right() { + let active_readarr_block = ActiveReadarrBlock::SystemLogs; + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app + .data + .readarr_data + .log_details + .set_items(vec!["t1".into(), "t22".into()]); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), "t1"); + assert_eq!( + app.data.readarr_data.log_details.items[1].to_string(), + "t22" + ); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), "1"); + assert_eq!(app.data.readarr_data.log_details.items[1].to_string(), "22"); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), ""); + assert_eq!(app.data.readarr_data.log_details.items[1].to_string(), "2"); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), ""); + assert_eq!(app.data.readarr_data.log_details.items[1].to_string(), ""); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), ""); + assert_eq!(app.data.readarr_data.log_details.items[1].to_string(), ""); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), "1"); + assert_eq!(app.data.readarr_data.log_details.items[1].to_string(), "2"); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), "t1"); + assert_eq!(app.data.readarr_data.log_details.items[1].to_string(), "22"); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.readarr_data.log_details.items[0].to_string(), "t1"); + assert_eq!( + app.data.readarr_data.log_details.items[1].to_string(), + "t22" + ); + } + + #[rstest] + fn test_left_right_prompt_toggle( + #[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + + SystemDetailsHandler::new( + key, + &mut app, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.readarr_data.prompt_confirm); + + SystemDetailsHandler::new( + key, + &mut app, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(!app.data.readarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use crate::assert_navigation_popped; + use crate::network::readarr_network::ReadarrEvent; + use pretty_assertions::assert_eq; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_system_tasks_submit() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.updates = updates_text(); + + SystemDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveReadarrBlock::SystemTasks, None) + .handle(); + + assert_navigation_pushed!(app, ActiveReadarrBlock::SystemTaskStartConfirmPrompt.into()); + } + + #[test] + fn test_system_tasks_submit_no_op_when_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + app.data.readarr_data.updates = updates_text(); + + SystemDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveReadarrBlock::SystemTasks, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveReadarrBlock::SystemTasks.into() + ); + } + + #[test] + fn test_system_tasks_start_task_prompt_confirm_submit() { + let mut app = App::test_default(); + app.data.readarr_data.updates = updates_text(); + app.data.readarr_data.prompt_confirm = true; + app.data.readarr_data.tasks.set_items(tasks_vec()); + app.data.readarr_data.tasks.select_index(Some(1)); + app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemTaskStartConfirmPrompt.into()); + + SystemDetailsHandler::new( + SUBMIT_KEY, + &mut app, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.readarr_data.prompt_confirm); + assert_some_eq_x!( + &app.data.readarr_data.prompt_confirm_action, + &ReadarrEvent::StartTask(ReadarrTaskName::RssSync) + ); + assert_navigation_popped!(app, ActiveReadarrBlock::SystemTasks.into()); + } + + #[test] + fn test_system_tasks_start_task_prompt_decline_submit() { + let mut app = App::test_default(); + app.data.readarr_data.updates = updates_text(); + app.data.readarr_data.tasks.set_items(tasks_vec()); + app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemTaskStartConfirmPrompt.into()); + + SystemDetailsHandler::new( + SUBMIT_KEY, + &mut app, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(!app.data.readarr_data.prompt_confirm); + assert_none!(app.data.readarr_data.prompt_confirm_action); + assert_navigation_popped!(app, ActiveReadarrBlock::SystemTasks.into()); + } + } + + mod test_handle_esc { + use crate::models::HorizontallyScrollableText; + use rstest::rstest; + + use super::*; + use crate::assert_navigation_popped; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_esc_system_logs(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.is_loading = is_loading; + app + .data + .readarr_data + .log_details + .set_items(vec![HorizontallyScrollableText::from("test")]); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemLogs.into()); + app + .data + .readarr_data + .log_details + .set_items(vec![HorizontallyScrollableText::default()]); + + SystemDetailsHandler::new(ESC_KEY, &mut app, ActiveReadarrBlock::SystemLogs, None).handle(); + + assert_navigation_popped!(app, ActiveReadarrBlock::System.into()); + assert_is_empty!(app.data.readarr_data.log_details.items); + } + + #[rstest] + fn test_esc_system_tasks(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.is_loading = is_loading; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + app.data.readarr_data.tasks.set_items(tasks_vec()); + + SystemDetailsHandler::new(ESC_KEY, &mut app, ActiveReadarrBlock::SystemTasks, None).handle(); + + assert_navigation_popped!(app, ActiveReadarrBlock::System.into()); + } + + #[rstest] + fn test_esc_system_queued_events(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.is_loading = is_loading; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemQueuedEvents.into()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + + SystemDetailsHandler::new( + ESC_KEY, + &mut app, + ActiveReadarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveReadarrBlock::System.into()); + } + + #[rstest] + fn test_esc_system_updates(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.is_loading = is_loading; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemUpdates.into()); + + SystemDetailsHandler::new(ESC_KEY, &mut app, ActiveReadarrBlock::SystemUpdates, None) + .handle(); + + assert_navigation_popped!(app, ActiveReadarrBlock::System.into()); + } + + #[test] + fn test_system_tasks_start_task_prompt_esc() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemTaskStartConfirmPrompt.into()); + app.data.readarr_data.prompt_confirm = true; + + SystemDetailsHandler::new( + ESC_KEY, + &mut app, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert_navigation_popped!(app, ActiveReadarrBlock::SystemTasks.into()); + assert!(!app.data.readarr_data.prompt_confirm); + } + } + + mod test_handle_key_char { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::network::readarr_network::ReadarrEvent; + + use super::*; + use crate::{assert_navigation_popped, assert_navigation_pushed}; + + #[rstest] + fn test_refresh_key( + #[values( + ActiveReadarrBlock::SystemLogs, + ActiveReadarrBlock::SystemTasks, + ActiveReadarrBlock::SystemQueuedEvents, + ActiveReadarrBlock::SystemUpdates + )] + active_readarr_block: ActiveReadarrBlock, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.updates = updates_text(); + app.push_navigation_stack(active_readarr_block.into()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_navigation_pushed!(app, active_readarr_block.into()); + assert!(app.should_refresh); + } + + #[rstest] + fn test_refresh_key_no_op_when_not_ready( + #[values( + ActiveReadarrBlock::SystemLogs, + ActiveReadarrBlock::SystemTasks, + ActiveReadarrBlock::SystemQueuedEvents, + ActiveReadarrBlock::SystemUpdates + )] + active_readarr_block: ActiveReadarrBlock, + ) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.updates = updates_text(); + app.push_navigation_stack(active_readarr_block.into()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + active_readarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_readarr_block.into()); + assert!(!app.should_refresh); + } + + #[test] + fn test_system_tasks_start_task_prompt_confirm() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.updates = updates_text(); + app.data.readarr_data.tasks.set_items(tasks_vec()); + app.data.readarr_data.tasks.select_index(Some(1)); + app.push_navigation_stack(ActiveReadarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveReadarrBlock::SystemTaskStartConfirmPrompt.into()); + + SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.readarr_data.prompt_confirm); + assert_eq!( + app.data.readarr_data.prompt_confirm_action, + Some(ReadarrEvent::StartTask(ReadarrTaskName::RssSync)) + ); + assert_navigation_popped!(app, ActiveReadarrBlock::SystemTasks.into()); + } + } + + #[test] + fn test_system_details_handler_accepts() { + ActiveReadarrBlock::iter().for_each(|active_readarr_block| { + if SYSTEM_DETAILS_BLOCKS.contains(&active_readarr_block) { + assert!(SystemDetailsHandler::accepts(active_readarr_block)); + } else { + assert!(!SystemDetailsHandler::accepts(active_readarr_block)); + } + }) + } + + #[rstest] + fn test_system_details_handler_ignore_special_keys( + #[values(true, false)] ignore_special_keys_for_textbox_input: bool, + ) { + let mut app = App::test_default(); + app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input; + let handler = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::default(), + None, + ); + + assert_eq!( + handler.ignore_special_keys(), + ignore_special_keys_for_textbox_input + ); + } + + #[test] + fn test_extract_task_name_reads_the_selected_task() { + let mut app = App::test_default(); + app.data.readarr_data.tasks.set_items(tasks_vec()); + app.data.readarr_data.tasks.select_index(Some(1)); + + let task_name = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ) + .extract_task_name(); + + assert_eq!(task_name, ReadarrTaskName::RssSync); + } + + #[test] + fn test_extract_task_name_reads_the_first_task_when_nothing_is_selected() { + let mut app = App::test_default(); + app.data.readarr_data.tasks.set_items(tasks_vec()); + + let task_name = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ) + .extract_task_name(); + + assert_eq!(task_name, ReadarrTaskName::Backup); + } + + #[test] + fn test_system_details_handler_not_ready_when_loading() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + app.data.readarr_data.updates = updates_text(); + + let handler = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_system_details_handler_not_ready_when_log_details_and_updates_and_tasks_are_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + + let handler = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_system_details_handler_ready_when_not_loading_and_log_details_is_not_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + app + .data + .readarr_data + .log_details + .set_items(vec![HorizontallyScrollableText::default()]); + + let handler = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ); + + assert!(handler.is_ready()); + } + + #[test] + fn test_system_details_handler_ready_when_not_loading_and_tasks_is_not_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + app.data.readarr_data.tasks.set_items(tasks_vec()); + + let handler = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemTasks, + None, + ); + + assert!(handler.is_ready()); + } + + #[test] + fn test_system_details_handler_ready_when_not_loading_and_updates_is_not_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + app.data.readarr_data.updates = updates_text(); + + let handler = SystemDetailsHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::SystemUpdates, + None, + ); + + assert!(handler.is_ready()); + } + + fn updates_text() -> ScrollableText { + ScrollableText::with_string("update note one\nupdate note two".to_owned()) + } + + fn tasks_vec() -> Vec { + vec![ + ReadarrTask { + name: "Backup task".to_owned(), + task_name: ReadarrTaskName::Backup, + ..ReadarrTask::default() + }, + ReadarrTask { + name: "Rss sync task".to_owned(), + task_name: ReadarrTaskName::RssSync, + ..ReadarrTask::default() + }, + ] + } + + fn extended_tasks_vec() -> Vec { + let mut tasks = tasks_vec(); + tasks.push(ReadarrTask { + name: "Housekeeping task".to_owned(), + task_name: ReadarrTaskName::Housekeeping, + ..ReadarrTask::default() + }); + + tasks + } + + fn queued_events_vec() -> Vec { + vec![ + QueueEvent { + name: "queued event one".to_owned(), + ..QueueEvent::default() + }, + QueueEvent { + name: "queued event two".to_owned(), + ..QueueEvent::default() + }, + ] + } + + fn extended_queued_events_vec() -> Vec { + let mut queued_events = queued_events_vec(); + queued_events.push(QueueEvent { + name: "queued event three".to_owned(), + ..QueueEvent::default() + }); + + queued_events + } +} diff --git a/src/handlers/readarr_handlers/system/system_handler_tests.rs b/src/handlers/readarr_handlers/system/system_handler_tests.rs new file mode 100644 index 00000000..4ffe1b0f --- /dev/null +++ b/src/handlers/readarr_handlers/system/system_handler_tests.rs @@ -0,0 +1,496 @@ +#[cfg(test)] +mod tests { + use rstest::rstest; + use strum::IntoEnumIterator; + + use crate::app::App; + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::assert_navigation_pushed; + use crate::event::Key; + use crate::handlers::KeyEventHandler; + use crate::handlers::readarr_handlers::system::SystemHandler; + use crate::models::HorizontallyScrollableText; + use crate::models::readarr_models::{ReadarrTask, ReadarrTaskName}; + use crate::models::servarr_data::readarr::readarr_data::{ + ActiveReadarrBlock, SYSTEM_DETAILS_BLOCKS, + }; + use crate::models::servarr_models::QueueEvent; + use crate::test_handler_delegation; + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + + use super::*; + use crate::assert_navigation_pushed; + + #[rstest] + fn test_system_tab_left(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = is_loading; + app.data.readarr_data.main_tabs.set_index(6); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.data.readarr_data.main_tabs.get_active_route(), + ActiveReadarrBlock::Indexers.into() + ); + assert_navigation_pushed!(app, ActiveReadarrBlock::Indexers.into()); + } + + #[rstest] + fn test_system_tab_right(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = is_loading; + app.data.readarr_data.main_tabs.set_index(6); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.data.readarr_data.main_tabs.get_active_route(), + ActiveReadarrBlock::Authors.into() + ); + assert_navigation_pushed!(app, ActiveReadarrBlock::Authors.into()); + } + } + + mod test_handle_esc { + + use super::*; + use crate::assert_navigation_popped; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_default_esc(#[values(true, false)] is_loading: bool) { + let mut app = App::test_default(); + app.is_loading = is_loading; + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + + SystemHandler::new(ESC_KEY, &mut app, ActiveReadarrBlock::System, None).handle(); + + assert_navigation_popped!(app, ActiveReadarrBlock::System.into()); + assert_is_empty!(app.error.text); + } + } + + mod test_handle_key_char { + use pretty_assertions::{assert_eq, assert_str_eq}; + + use super::*; + + #[test] + fn test_update_system_key() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_navigation_pushed!(app, ActiveReadarrBlock::SystemUpdates.into()); + } + + #[test] + fn test_update_system_key_no_op_if_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveReadarrBlock::System.into()); + } + + #[test] + fn test_queued_events_key() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.events.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_navigation_pushed!(app, ActiveReadarrBlock::SystemQueuedEvents.into()); + } + + #[test] + fn test_queued_events_key_no_op_if_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.events.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveReadarrBlock::System.into()); + } + + #[test] + fn test_refresh_system_key() { + let mut app = App::test_default(); + seed_system_data(&mut app); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_navigation_pushed!(app, ActiveReadarrBlock::System.into()); + assert!(app.should_refresh); + } + + #[test] + fn test_refresh_system_key_no_op_if_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + seed_system_data(&mut app); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveReadarrBlock::System.into()); + assert!(!app.should_refresh); + } + + #[test] + fn test_logs_key() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.logs.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_navigation_pushed!(app, ActiveReadarrBlock::SystemLogs.into()); + assert_eq!( + app.data.readarr_data.log_details.items, + app.data.readarr_data.logs.items + ); + assert_str_eq!( + app.data.readarr_data.log_details.current_selection().text, + "log line gamma" + ); + } + + #[test] + fn test_logs_key_no_op_when_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.logs.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveReadarrBlock::System.into()); + assert_is_empty!(app.data.readarr_data.log_details); + } + + #[test] + fn test_tasks_key() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.tasks.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_navigation_pushed!(app, ActiveReadarrBlock::SystemTasks.into()); + } + + #[test] + fn test_tasks_key_no_op_when_not_ready() { + let mut app = App::test_default(); + app.is_loading = true; + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.tasks.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveReadarrBlock::System.into()); + } + + #[test] + fn test_unmapped_key_is_a_no_op() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + seed_system_data(&mut app); + + SystemHandler::new( + DEFAULT_KEYBINDINGS.add.key, + &mut app, + ActiveReadarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveReadarrBlock::System.into()); + assert!(!app.should_refresh); + assert_is_empty!(app.data.readarr_data.log_details); + } + } + + #[rstest] + fn test_delegates_system_details_blocks_to_system_details_handler( + #[values( + ActiveReadarrBlock::SystemLogs, + ActiveReadarrBlock::SystemQueuedEvents, + ActiveReadarrBlock::SystemTasks, + ActiveReadarrBlock::SystemTaskStartConfirmPrompt, + ActiveReadarrBlock::SystemUpdates + )] + active_readarr_block: ActiveReadarrBlock, + ) { + test_handler_delegation!( + SystemHandler, + ActiveReadarrBlock::System, + active_readarr_block + ); + } + + #[test] + fn test_system_handler_accepts() { + let mut system_blocks = vec![ActiveReadarrBlock::System]; + system_blocks.extend(SYSTEM_DETAILS_BLOCKS); + + ActiveReadarrBlock::iter().for_each(|active_readarr_block| { + if system_blocks.contains(&active_readarr_block) { + assert!(SystemHandler::accepts(active_readarr_block)); + } else { + assert!(!SystemHandler::accepts(active_readarr_block)); + } + }) + } + + #[rstest] + fn test_system_handler_ignore_special_keys( + #[values(true, false)] ignore_special_keys_for_textbox_input: bool, + ) { + let mut app = App::test_default(); + app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input; + let handler = SystemHandler::new( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveReadarrBlock::default(), + None, + ); + + assert_eq!( + handler.ignore_special_keys(), + ignore_special_keys_for_textbox_input + ); + } + + #[test] + fn test_system_handler_is_not_ready_when_loading() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = true; + seed_system_data(&mut app); + + let system_handler = SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_not_ready_when_logs_is_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + app.data.readarr_data.tasks.set_items(tasks_vec()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + + let system_handler = SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_not_ready_when_tasks_is_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + app.data.readarr_data.logs.set_items(logs_vec()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + + let system_handler = SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_not_ready_when_queued_events_is_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + app.data.readarr_data.logs.set_items(logs_vec()); + app.data.readarr_data.tasks.set_items(tasks_vec()); + + let system_handler = SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_ready_when_all_required_tables_are_not_empty() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.is_loading = false; + seed_system_data(&mut app); + + let system_handler = SystemHandler::new( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveReadarrBlock::System, + None, + ); + + assert!(system_handler.is_ready()); + } + + fn seed_system_data(app: &mut App<'_>) { + app.data.readarr_data.logs.set_items(logs_vec()); + app + .data + .readarr_data + .queued_events + .set_items(queued_events_vec()); + app.data.readarr_data.tasks.set_items(tasks_vec()); + } + + fn logs_vec() -> Vec { + vec![ + HorizontallyScrollableText::from("log line alpha"), + HorizontallyScrollableText::from("log line beta"), + HorizontallyScrollableText::from("log line gamma"), + ] + } + + fn queued_events_vec() -> Vec { + vec![ + QueueEvent { + name: "queued event one".to_owned(), + ..QueueEvent::default() + }, + QueueEvent { + name: "queued event two".to_owned(), + ..QueueEvent::default() + }, + ] + } + + fn tasks_vec() -> Vec { + vec![ + ReadarrTask { + name: "Backup task".to_owned(), + task_name: ReadarrTaskName::Backup, + ..ReadarrTask::default() + }, + ReadarrTask { + name: "Rss sync task".to_owned(), + task_name: ReadarrTaskName::RssSync, + ..ReadarrTask::default() + }, + ] + } +}