From 2d251554adaab5aeb3be1ec8aae3f57b59a2419f Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 4 Dec 2024 17:04:36 -0700 Subject: [PATCH] feat(handler): System handler support for Sonarr --- src/handlers/sonarr_handlers/mod.rs | 5 + .../sonarr_handlers/sonarr_handler_tests.rs | 19 + src/handlers/sonarr_handlers/system/mod.rs | 123 ++ .../system/system_details_handler.rs | 180 +++ .../system/system_details_handler_tests.rs | 1020 +++++++++++++++++ .../system/system_handler_tests.rs | 572 +++++++++ src/models/servarr_data/sonarr/sonarr_data.rs | 8 + .../servarr_data/sonarr/sonarr_data_tests.rs | 11 + 8 files changed, 1938 insertions(+) create mode 100644 src/handlers/sonarr_handlers/system/mod.rs create mode 100644 src/handlers/sonarr_handlers/system/system_details_handler.rs create mode 100644 src/handlers/sonarr_handlers/system/system_details_handler_tests.rs create mode 100644 src/handlers/sonarr_handlers/system/system_handler_tests.rs diff --git a/src/handlers/sonarr_handlers/mod.rs b/src/handlers/sonarr_handlers/mod.rs index eca86a0..233a2bf 100644 --- a/src/handlers/sonarr_handlers/mod.rs +++ b/src/handlers/sonarr_handlers/mod.rs @@ -4,6 +4,7 @@ use history::HistoryHandler; use indexers::IndexersHandler; use library::LibraryHandler; use root_folders::RootFoldersHandler; +use system::SystemHandler; use crate::{ app::{key_binding::DEFAULT_KEYBINDINGS, App}, @@ -19,6 +20,7 @@ mod history; mod indexers; mod library; mod root_folders; +mod system; #[cfg(test)] #[path = "sonarr_handler_tests.rs"] @@ -57,6 +59,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b _ if IndexersHandler::accepts(self.active_sonarr_block) => { IndexersHandler::with(self.key, self.app, self.active_sonarr_block, self.context).handle() } + _ if SystemHandler::accepts(self.active_sonarr_block) => { + SystemHandler::with(self.key, self.app, self.active_sonarr_block, self.context).handle() + } _ => self.handle_key_event(), } } diff --git a/src/handlers/sonarr_handlers/sonarr_handler_tests.rs b/src/handlers/sonarr_handlers/sonarr_handler_tests.rs index 1e1702f..e8dae31 100644 --- a/src/handlers/sonarr_handlers/sonarr_handler_tests.rs +++ b/src/handlers/sonarr_handlers/sonarr_handler_tests.rs @@ -202,4 +202,23 @@ mod tests { active_sonarr_block ); } + + #[rstest] + fn test_delegates_system_blocks_to_system_handler( + #[values( + ActiveSonarrBlock::System, + ActiveSonarrBlock::SystemLogs, + ActiveSonarrBlock::SystemQueuedEvents, + ActiveSonarrBlock::SystemTasks, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + ActiveSonarrBlock::SystemUpdates + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + test_handler_delegation!( + SonarrHandler, + ActiveSonarrBlock::System, + active_sonarr_block + ); + } } diff --git a/src/handlers/sonarr_handlers/system/mod.rs b/src/handlers/sonarr_handlers/system/mod.rs new file mode 100644 index 0000000..83e939a --- /dev/null +++ b/src/handlers/sonarr_handlers/system/mod.rs @@ -0,0 +1,123 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::App; +use crate::event::Key; +use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; +use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler; +use crate::handlers::{handle_clear_errors, KeyEventHandler}; +use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; +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: Key, + app: &'a mut App<'b>, + active_sonarr_block: ActiveSonarrBlock, + context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b> { + fn handle(&mut self) { + match self.active_sonarr_block { + _ if SystemDetailsHandler::accepts(self.active_sonarr_block) => { + SystemDetailsHandler::with(self.key, self.app, self.active_sonarr_block, self.context) + .handle() + } + _ => self.handle_key_event(), + } + } + + fn accepts(active_block: ActiveSonarrBlock) -> bool { + SystemDetailsHandler::accepts(active_block) || active_block == ActiveSonarrBlock::System + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + context: Option, + ) -> SystemHandler<'a, 'b> { + SystemHandler { + key, + app, + active_sonarr_block: active_block, + context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading + && !self.app.data.sonarr_data.logs.is_empty() + && !self.app.data.sonarr_data.queued_events.is_empty() + && !self.app.data.sonarr_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_sonarr_block == ActiveSonarrBlock::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_sonarr_block == ActiveSonarrBlock::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(ActiveSonarrBlock::SystemQueuedEvents.into()); + } + _ if key == DEFAULT_KEYBINDINGS.logs.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::SystemLogs.into()); + self + .app + .data + .sonarr_data + .log_details + .set_items(self.app.data.sonarr_data.logs.items.to_vec()); + self.app.data.sonarr_data.log_details.scroll_to_bottom(); + } + _ if key == DEFAULT_KEYBINDINGS.tasks.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + } + _ if key == DEFAULT_KEYBINDINGS.update.key => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into()); + } + _ => (), + } + } + } +} diff --git a/src/handlers/sonarr_handlers/system/system_details_handler.rs b/src/handlers/sonarr_handlers/system/system_details_handler.rs new file mode 100644 index 0000000..2cb2441 --- /dev/null +++ b/src/handlers/sonarr_handlers/system/system_details_handler.rs @@ -0,0 +1,180 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::app::App; +use crate::event::Key; +use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS}; +use crate::models::stateful_list::StatefulList; +use crate::models::Scrollable; +use crate::network::sonarr_network::SonarrEvent; + +#[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_sonarr_block: ActiveSonarrBlock, + _context: Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler<'a, 'b> { + fn accepts(active_block: ActiveSonarrBlock) -> bool { + SYSTEM_DETAILS_BLOCKS.contains(&active_block) + } + + fn with( + key: Key, + app: &'a mut App<'b>, + active_block: ActiveSonarrBlock, + context: Option, + ) -> SystemDetailsHandler<'a, 'b> { + SystemDetailsHandler { + key, + app, + active_sonarr_block: active_block, + _context: context, + } + } + + fn get_key(&self) -> Key { + self.key + } + + fn is_ready(&self) -> bool { + !self.app.is_loading + && (!self.app.data.sonarr_data.log_details.is_empty() + || !self.app.data.sonarr_data.updates.is_empty()) + } + + fn handle_scroll_up(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::SystemLogs => self.app.data.sonarr_data.log_details.scroll_up(), + ActiveSonarrBlock::SystemTasks => self.app.data.sonarr_data.tasks.scroll_up(), + ActiveSonarrBlock::SystemUpdates => self.app.data.sonarr_data.updates.scroll_up(), + ActiveSonarrBlock::SystemQueuedEvents => self.app.data.sonarr_data.queued_events.scroll_up(), + _ => (), + } + } + + fn handle_scroll_down(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::SystemLogs => self.app.data.sonarr_data.log_details.scroll_down(), + ActiveSonarrBlock::SystemTasks => self.app.data.sonarr_data.tasks.scroll_down(), + ActiveSonarrBlock::SystemUpdates => self.app.data.sonarr_data.updates.scroll_down(), + ActiveSonarrBlock::SystemQueuedEvents => { + self.app.data.sonarr_data.queued_events.scroll_down() + } + _ => (), + } + } + + fn handle_home(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::SystemLogs => self.app.data.sonarr_data.log_details.scroll_to_top(), + ActiveSonarrBlock::SystemTasks => self.app.data.sonarr_data.tasks.scroll_to_top(), + ActiveSonarrBlock::SystemUpdates => self.app.data.sonarr_data.updates.scroll_to_top(), + ActiveSonarrBlock::SystemQueuedEvents => { + self.app.data.sonarr_data.queued_events.scroll_to_top() + } + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::SystemLogs => self.app.data.sonarr_data.log_details.scroll_to_bottom(), + ActiveSonarrBlock::SystemTasks => self.app.data.sonarr_data.tasks.scroll_to_bottom(), + ActiveSonarrBlock::SystemUpdates => self.app.data.sonarr_data.updates.scroll_to_bottom(), + ActiveSonarrBlock::SystemQueuedEvents => { + self.app.data.sonarr_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_sonarr_block { + ActiveSonarrBlock::SystemLogs => match self.key { + _ if key == DEFAULT_KEYBINDINGS.left.key => { + self + .app + .data + .sonarr_data + .log_details + .items + .iter() + .for_each(|log| log.scroll_right()); + } + _ if key == DEFAULT_KEYBINDINGS.right.key => { + self + .app + .data + .sonarr_data + .log_details + .items + .iter() + .for_each(|log| log.scroll_left()); + } + _ => (), + }, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt => handle_prompt_toggle(self.app, self.key), + _ => (), + } + } + + fn handle_submit(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::SystemTasks => { + self + .app + .push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); + } + ActiveSonarrBlock::SystemTaskStartConfirmPrompt => { + if self.app.data.sonarr_data.prompt_confirm { + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::StartTask(None)); + } + + self.app.pop_navigation_stack(); + } + _ => (), + } + } + + fn handle_esc(&mut self) { + match self.active_sonarr_block { + ActiveSonarrBlock::SystemLogs => { + self.app.data.sonarr_data.log_details = StatefulList::default(); + self.app.pop_navigation_stack() + } + ActiveSonarrBlock::SystemQueuedEvents + | ActiveSonarrBlock::SystemTasks + | ActiveSonarrBlock::SystemUpdates => self.app.pop_navigation_stack(), + ActiveSonarrBlock::SystemTaskStartConfirmPrompt => { + self.app.pop_navigation_stack(); + self.app.data.sonarr_data.prompt_confirm = false; + } + _ => (), + } + } + + fn handle_char_key_event(&mut self) { + if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) + && self.key == DEFAULT_KEYBINDINGS.refresh.key + { + self.app.should_refresh = true; + } + + if self.active_sonarr_block == ActiveSonarrBlock::SystemTaskStartConfirmPrompt + && self.key == DEFAULT_KEYBINDINGS.confirm.key + { + self.app.data.sonarr_data.prompt_confirm = true; + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::StartTask(None)); + self.app.pop_navigation_stack(); + } + } +} diff --git a/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs b/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs new file mode 100644 index 0000000..a8a908a --- /dev/null +++ b/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs @@ -0,0 +1,1020 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + use strum::IntoEnumIterator; + + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS, + }; + use crate::models::servarr_models::QueueEvent; + use crate::models::sonarr_models::SonarrTask; + use crate::models::{HorizontallyScrollableText, ScrollableText}; + + mod test_handle_scroll_up_and_down { + use rstest::rstest; + + use crate::models::{HorizontallyScrollableText, ScrollableText}; + use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; + + use super::*; + + test_iterable_scroll!( + test_log_details_scroll, + SystemDetailsHandler, + sonarr_data, + log_details, + simple_stateful_iterable_vec!(HorizontallyScrollableText, String, text), + ActiveSonarrBlock::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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app + .data + .sonarr_data + .log_details + .set_items(simple_stateful_iterable_vec!( + HorizontallyScrollableText, + String, + text + )); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemLogs, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.log_details.current_selection().text, + "Test 1" + ); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemLogs, None).handle(); + + assert_str_eq!( + app.data.sonarr_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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + app + .data + .sonarr_data + .tasks + .set_items(simple_stateful_iterable_vec!(SonarrTask, String, name)); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 2" + ); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 1" + ); + } + + #[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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + app + .data + .sonarr_data + .tasks + .set_items(simple_stateful_iterable_vec!(SonarrTask, String, name)); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 1" + ); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemTasks, None).handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 1" + ); + } + + #[rstest] + fn test_queued_events_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + app + .data + .sonarr_data + .queued_events + .set_items(simple_stateful_iterable_vec!(QueueEvent, String, name)); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 2" + ); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 1" + ); + } + + #[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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + app + .data + .sonarr_data + .queued_events + .set_items(simple_stateful_iterable_vec!(QueueEvent, String, name)); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 1" + ); + + SystemDetailsHandler::with(key, &mut app, ActiveSonarrBlock::SystemQueuedEvents, None) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 1" + ); + } + + #[test] + fn test_system_updates_scroll() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.up.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 0); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.down.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 1); + } + + #[test] + fn test_system_updates_scroll_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.up.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 0); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.down.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 0); + } + } + + mod test_handle_home_end { + use crate::models::{HorizontallyScrollableText, ScrollableText}; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + + use super::*; + + test_iterable_home_and_end!( + test_log_details_home_end, + SystemDetailsHandler, + sonarr_data, + log_details, + extended_stateful_iterable_vec!(HorizontallyScrollableText, String, text), + ActiveSonarrBlock::SystemLogs, + None, + text + ); + + #[test] + fn test_log_details_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app + .data + .sonarr_data + .log_details + .set_items(extended_stateful_iterable_vec!( + HorizontallyScrollableText, + String, + text + )); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemLogs, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.log_details.current_selection().text, + "Test 1" + ); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemLogs, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.log_details.current_selection().text, + "Test 1" + ); + } + + #[test] + fn test_tasks_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = + ScrollableText::with_string("Test 1\nTest 2\nTest 3".to_owned()); + app + .data + .sonarr_data + .tasks + .set_items(extended_stateful_iterable_vec!(SonarrTask, String, name)); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 3" + ); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 1" + ); + } + + #[test] + fn test_tasks_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = + ScrollableText::with_string("Test 1\nTest 2\nTest 3".to_owned()); + app + .data + .sonarr_data + .tasks + .set_items(extended_stateful_iterable_vec!(SonarrTask, String, name)); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 1" + ); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemTasks, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.tasks.current_selection().name, + "Test 1" + ); + } + + #[test] + fn test_queued_events_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = + ScrollableText::with_string("Test 1\nTest 2\nTest 3".to_owned()); + app + .data + .sonarr_data + .queued_events + .set_items(extended_stateful_iterable_vec!(QueueEvent, String, name)); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 3" + ); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 1" + ); + } + + #[test] + fn test_queued_events_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = + ScrollableText::with_string("Test 1\nTest 2\nTest 3".to_owned()); + app + .data + .sonarr_data + .queued_events + .set_items(extended_stateful_iterable_vec!(QueueEvent, String, name)); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 1" + ); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_str_eq!( + app.data.sonarr_data.queued_events.current_selection().name, + "Test 1" + ); + } + + #[test] + fn test_system_updates_home_end() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 1); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 0); + } + + #[test] + fn test_system_updates_home_end_no_op_when_not_ready() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = ScrollableText::with_string("Test 1\nTest 2".to_owned()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.updates.offset, 0); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_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_sonarr_block = ActiveSonarrBlock::SystemLogs; + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app + .data + .sonarr_data + .log_details + .set_items(vec!["t1".into(), "t22".into()]); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), "t1"); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), "t22"); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), "1"); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), "22"); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), ""); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), "2"); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), ""); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), ""); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), ""); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), ""); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), "1"); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), "2"); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), "t1"); + assert_eq!(app.data.sonarr_data.log_details.items[1].to_string(), "22"); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.data.sonarr_data.log_details.items[0].to_string(), "t1"); + assert_eq!(app.data.sonarr_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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + + SystemDetailsHandler::with( + key, + &mut app, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + + SystemDetailsHandler::with( + key, + &mut app, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + + use crate::network::sonarr_network::SonarrEvent; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_system_tasks_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + + SystemDetailsHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SystemTasks, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into() + ); + } + + #[test] + fn test_system_tasks_submit_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + + SystemDetailsHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SystemTasks, None) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTasks.into() + ); + } + + #[test] + fn test_system_tasks_start_task_prompt_confirm_submit() { + let mut app = App::default(); + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app.data.sonarr_data.prompt_confirm = true; + app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); + + SystemDetailsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::StartTask(None)) + ); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTasks.into() + ); + } + + #[test] + fn test_system_tasks_start_task_prompt_decline_submit() { + let mut app = App::default(); + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); + + SystemDetailsHandler::with( + SUBMIT_KEY, + &mut app, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTasks.into() + ); + } + } + + mod test_handle_esc { + use crate::models::HorizontallyScrollableText; + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_esc_system_logs(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app + .data + .sonarr_data + .log_details + .set_items(vec![HorizontallyScrollableText::from("test")]); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemLogs.into()); + app + .data + .sonarr_data + .log_details + .set_items(vec![HorizontallyScrollableText::default()]); + + SystemDetailsHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::SystemLogs, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + assert!(app.data.sonarr_data.log_details.items.is_empty()); + } + + #[rstest] + fn test_esc_system_tasks(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemDetailsHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::SystemTasks, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + + #[rstest] + fn test_esc_system_queued_events(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into()); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + + SystemDetailsHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::SystemQueuedEvents, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + + #[rstest] + fn test_esc_system_updates(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.is_loading = is_ready; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into()); + + SystemDetailsHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::SystemUpdates, None) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + + #[test] + fn test_system_tasks_start_task_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); + app.data.sonarr_data.prompt_confirm = true; + + SystemDetailsHandler::with( + ESC_KEY, + &mut app, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTasks.into() + ); + assert!(!app.data.sonarr_data.prompt_confirm); + } + } + + mod test_handle_key_char { + use rstest::rstest; + + use crate::network::sonarr_network::SonarrEvent; + + use super::*; + + #[rstest] + fn test_refresh_key( + #[values( + ActiveSonarrBlock::SystemLogs, + ActiveSonarrBlock::SystemTasks, + ActiveSonarrBlock::SystemQueuedEvents, + ActiveSonarrBlock::SystemUpdates + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app.push_navigation_stack(active_sonarr_block.into()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_sonarr_block.into()); + assert!(app.should_refresh); + } + + #[rstest] + fn test_refresh_key_no_op_when_not_ready( + #[values( + ActiveSonarrBlock::SystemLogs, + ActiveSonarrBlock::SystemTasks, + ActiveSonarrBlock::SystemQueuedEvents, + ActiveSonarrBlock::SystemUpdates + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app.push_navigation_stack(active_sonarr_block.into()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + active_sonarr_block, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), active_sonarr_block.into()); + assert!(!app.should_refresh); + } + + #[test] + fn test_system_tasks_start_task_prompt_confirm() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); + app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); + + SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + None, + ) + .handle(); + + assert!(app.data.sonarr_data.prompt_confirm); + assert_eq!( + app.data.sonarr_data.prompt_confirm_action, + Some(SonarrEvent::StartTask(None)) + ); + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTasks.into() + ); + } + } + + #[test] + fn test_system_details_handler_accepts() { + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if SYSTEM_DETAILS_BLOCKS.contains(&active_sonarr_block) { + assert!(SystemDetailsHandler::accepts(active_sonarr_block)); + } else { + assert!(!SystemDetailsHandler::accepts(active_sonarr_block)); + } + }) + } + + #[test] + fn test_system_details_handler_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + + let handler = SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ); + + assert!(!handler.is_ready()); + } + + #[test] + fn test_system_details_handler_not_ready_when_both_log_details_and_updates_are_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + + let handler = SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + app + .data + .sonarr_data + .log_details + .set_items(vec![HorizontallyScrollableText::default()]); + + let handler = SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ); + + assert!(handler.is_ready()); + } + + #[test] + fn test_system_details_handler_ready_when_not_loading_and_updates_is_not_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + + let handler = SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SystemUpdates, + None, + ); + + assert!(handler.is_ready()); + } +} diff --git a/src/handlers/sonarr_handlers/system/system_handler_tests.rs b/src/handlers/sonarr_handlers/system/system_handler_tests.rs new file mode 100644 index 0000000..3f62287 --- /dev/null +++ b/src/handlers/sonarr_handlers/system/system_handler_tests.rs @@ -0,0 +1,572 @@ +#[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::App; + use crate::event::Key; + use crate::handlers::sonarr_handlers::system::SystemHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::sonarr_data::{ + ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS, + }; + use crate::models::servarr_models::QueueEvent; + use crate::models::sonarr_models::SonarrTask; + use crate::test_handler_delegation; + + mod test_handle_left_right_action { + use pretty_assertions::assert_eq; + + use super::*; + + #[rstest] + fn test_system_tab_left(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = is_ready; + app.data.sonarr_data.main_tabs.set_index(6); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.left.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + ActiveSonarrBlock::Indexers.into() + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); + } + + #[rstest] + fn test_system_tab_right(#[values(true, false)] is_ready: bool) { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = is_ready; + app.data.sonarr_data.main_tabs.set_index(6); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.right.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.data.sonarr_data.main_tabs.get_active_route(), + ActiveSonarrBlock::Series.into() + ); + assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use super::*; + + const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; + + #[rstest] + fn test_default_esc(#[values(true, false)] is_loading: bool) { + let mut app = App::default(); + app.is_loading = is_loading; + app.error = "test error".to_owned().into(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + + SystemHandler::with(ESC_KEY, &mut app, ActiveSonarrBlock::System, None).handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + assert!(app.error.text.is_empty()); + } + } + + mod test_handle_key_char { + use pretty_assertions::{assert_eq, assert_str_eq}; + + use crate::models::HorizontallyScrollableText; + + use super::*; + + #[test] + fn test_update_system_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemUpdates.into() + ); + } + + #[test] + fn test_update_system_key_no_op_if_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + + #[test] + fn test_queued_events_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.events.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemQueuedEvents.into() + ); + } + + #[test] + fn test_queued_events_key_no_op_if_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.events.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + + #[test] + fn test_refresh_system_key() { + let mut app = App::default(); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + assert!(app.should_refresh); + } + + #[test] + fn test_refresh_system_key_no_op_if_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.refresh.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + assert!(!app.should_refresh); + } + + #[test] + fn test_logs_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.logs.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemLogs.into() + ); + assert_eq!( + app.data.sonarr_data.log_details.items, + app.data.sonarr_data.logs.items + ); + assert_str_eq!( + app.data.sonarr_data.log_details.current_selection().text, + "test 2" + ); + } + + #[test] + fn test_logs_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.logs.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + assert!(app.data.sonarr_data.log_details.is_empty()); + } + + #[test] + fn test_tasks_key() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.tasks.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + ActiveSonarrBlock::SystemTasks.into() + ); + } + + #[test] + fn test_tasks_key_no_op_when_not_ready() { + let mut app = App::default(); + app.is_loading = true; + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(vec![ + HorizontallyScrollableText::from("test 1"), + HorizontallyScrollableText::from("test 2"), + ]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + SystemHandler::with( + DEFAULT_KEYBINDINGS.tasks.key, + &mut app, + ActiveSonarrBlock::System, + None, + ) + .handle(); + + assert_eq!(app.get_current_route(), ActiveSonarrBlock::System.into()); + } + } + + #[rstest] + fn test_delegates_system_details_blocks_to_system_details_handler( + #[values( + ActiveSonarrBlock::SystemLogs, + ActiveSonarrBlock::SystemQueuedEvents, + ActiveSonarrBlock::SystemTasks, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + ActiveSonarrBlock::SystemUpdates + )] + active_sonarr_block: ActiveSonarrBlock, + ) { + test_handler_delegation!( + SystemHandler, + ActiveSonarrBlock::System, + active_sonarr_block + ); + } + + #[test] + fn test_system_handler_accepts() { + let mut system_blocks = vec![ActiveSonarrBlock::System]; + system_blocks.extend(SYSTEM_DETAILS_BLOCKS); + + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if system_blocks.contains(&active_sonarr_block) { + assert!(SystemHandler::accepts(active_sonarr_block)); + } else { + assert!(!SystemHandler::accepts(active_sonarr_block)); + } + }) + } + + #[test] + fn test_system_handler_is_not_ready_when_loading() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = true; + + let system_handler = SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_not_ready_when_logs_is_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + + let system_handler = SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_not_ready_when_tasks_is_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + app.data.sonarr_data.logs.set_items(vec!["test".into()]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + + let system_handler = SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::System, + None, + ); + + assert!(!system_handler.is_ready()); + } + + #[test] + fn test_system_handler_is_not_ready_when_queued_events_is_empty() { + let mut app = App::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + app.data.sonarr_data.logs.set_items(vec!["test".into()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + let system_handler = SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::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::default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.is_loading = false; + app.data.sonarr_data.logs.set_items(vec!["test".into()]); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + app + .data + .sonarr_data + .queued_events + .set_items(vec![QueueEvent::default()]); + + let system_handler = SystemHandler::with( + DEFAULT_KEYBINDINGS.update.key, + &mut app, + ActiveSonarrBlock::System, + None, + ); + + assert!(system_handler.is_ready()); + } +} diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index e736f10..acec1c2 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -483,6 +483,14 @@ pub static INDEXERS_BLOCKS: [ActiveSonarrBlock; 3] = [ ActiveSonarrBlock::TestIndexer, ]; +pub static SYSTEM_DETAILS_BLOCKS: [ActiveSonarrBlock; 5] = [ + ActiveSonarrBlock::SystemLogs, + ActiveSonarrBlock::SystemQueuedEvents, + ActiveSonarrBlock::SystemTasks, + ActiveSonarrBlock::SystemTaskStartConfirmPrompt, + ActiveSonarrBlock::SystemUpdates, +]; + impl From for Route { fn from(active_sonarr_block: ActiveSonarrBlock) -> Route { Route::Sonarr(active_sonarr_block, None) diff --git a/src/models/servarr_data/sonarr/sonarr_data_tests.rs b/src/models/servarr_data/sonarr/sonarr_data_tests.rs index c0aceec..0e73f33 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -207,6 +207,7 @@ mod tests { EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_SERIES_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS, HISTORY_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS, ROOT_FOLDERS_BLOCKS, + SYSTEM_DETAILS_BLOCKS, }; #[test] @@ -556,5 +557,15 @@ mod tests { assert!(INDEXERS_BLOCKS.contains(&ActiveSonarrBlock::Indexers)); assert!(INDEXERS_BLOCKS.contains(&ActiveSonarrBlock::TestIndexer)); } + + #[test] + fn test_system_details_blocks_contents() { + assert_eq!(SYSTEM_DETAILS_BLOCKS.len(), 5); + assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SystemLogs)); + assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SystemQueuedEvents)); + assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SystemTasks)); + assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SystemTaskStartConfirmPrompt)); + assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SystemUpdates)); + } } }