feat(models): Added the necessary contextual help and tabs for the Sonarr UI

This commit is contained in:
2024-12-01 12:05:20 -07:00
parent f7c96d81e9
commit 21911f93d1
7 changed files with 545 additions and 17 deletions
+55
View File
@@ -31,3 +31,58 @@ pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
), ),
(DEFAULT_KEYBINDINGS.esc, "cancel filter"), (DEFAULT_KEYBINDINGS.esc, "cancel filter"),
]; ];
pub static SERIES_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.search, "auto search"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.search, "auto search"),
(DEFAULT_KEYBINDINGS.delete, "delete episode"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.search, "auto search"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.search, "auto search"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 1] =
[(DEFAULT_KEYBINDINGS.submit, "details")];
pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.search, "auto search"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
+157 -1
View File
@@ -4,7 +4,12 @@ mod tests {
use crate::app::{ use crate::app::{
key_binding::DEFAULT_KEYBINDINGS, key_binding::DEFAULT_KEYBINDINGS,
sonarr::sonarr_context_clues::{HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES}, sonarr::sonarr_context_clues::{
EPISODE_DETAILS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES,
MANUAL_SEASON_SEARCH_CONTEXT_CLUES, SEASON_DETAILS_CONTEXT_CLUES, SERIES_CONTEXT_CLUES,
SERIES_DETAILS_CONTEXT_CLUES,
},
}; };
#[test] #[test]
@@ -98,4 +103,155 @@ mod tests {
assert_str_eq!(*description, "cancel filter"); assert_str_eq!(*description, "cancel filter");
assert_eq!(history_context_clues_iter.next(), None); assert_eq!(history_context_clues_iter.next(), None);
} }
#[test]
fn test_series_details_context_clues() {
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, "auto search");
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(series_details_context_clues_iter.next(), None);
}
#[test]
fn test_season_details_context_clues() {
let mut season_details_context_clues_iter = SEASON_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, "auto search");
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
assert_str_eq!(*description, "delete episode");
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(season_details_context_clues_iter.next(), None);
}
#[test]
fn test_manual_season_search_context_clues() {
let mut manual_season_search_context_clues_iter = MANUAL_SEASON_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, "auto search");
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(manual_season_search_context_clues_iter.next(), None);
}
#[test]
fn test_manual_episode_search_context_clues() {
let mut manual_episode_search_context_clues_iter = MANUAL_EPISODE_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, "auto search");
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(manual_episode_search_context_clues_iter.next(), None);
}
#[test]
fn test_manual_episode_search_contextual_context_clues() {
let mut manual_search_contextual_context_clues_iter =
MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_search_contextual_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
assert_eq!(manual_search_contextual_context_clues_iter.next(), None);
}
#[test]
fn test_episode_details_context_clues() {
let mut episode_details_context_clues_iter = EPISODE_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, "auto search");
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(episode_details_context_clues_iter.next(), None);
}
} }
+87 -13
View File
@@ -1,15 +1,25 @@
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::models::{ use crate::{
servarr_data::modals::EditIndexerModal, app::{
servarr_models::{Indexer, RootFolder}, context_clues::build_context_clue_string,
sonarr_models::{Episode, Series, SeriesMonitor, SeriesType, SonarrHistoryItem, SonarrRelease}, sonarr::sonarr_context_clues::{
stateful_list::StatefulList, EPISODE_DETAILS_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
stateful_table::StatefulTable, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
HorizontallyScrollableText, ScrollableText, SEASON_DETAILS_CONTEXT_CLUES,
},
},
models::{
servarr_data::modals::EditIndexerModal,
servarr_models::{Indexer, RootFolder},
sonarr_models::{Episode, Series, SeriesMonitor, SeriesType, SonarrHistoryItem, SonarrRelease},
stateful_list::StatefulList,
stateful_table::StatefulTable,
HorizontallyScrollableText, ScrollableText, TabRoute, TabState,
},
}; };
use super::sonarr_data::SonarrData; use super::sonarr_data::{ActiveSonarrBlock, SonarrData};
#[cfg(test)] #[cfg(test)]
#[path = "modals_tests.rs"] #[path = "modals_tests.rs"]
@@ -246,22 +256,86 @@ impl From<&SonarrData<'_>> for EditSeriesModal {
} }
} }
#[derive(Default)]
pub struct EpisodeDetailsModal { pub struct EpisodeDetailsModal {
// Temporarily allowing this, since the value is only current written and not read.
// This will be read from once I begin the UI work for Sonarr
#[allow(dead_code)]
pub episode_details: ScrollableText, pub episode_details: ScrollableText,
pub file_details: String, pub file_details: String,
pub audio_details: String, pub audio_details: String,
pub video_details: String, pub video_details: String,
pub episode_history: StatefulTable<SonarrHistoryItem>, pub episode_history: StatefulTable<SonarrHistoryItem>,
pub episode_releases: StatefulTable<SonarrRelease>, pub episode_releases: StatefulTable<SonarrRelease>,
pub episode_details_tabs: TabState,
}
impl Default for EpisodeDetailsModal {
fn default() -> EpisodeDetailsModal {
EpisodeDetailsModal {
episode_details: ScrollableText::default(),
file_details: String::new(),
audio_details: String::new(),
video_details: String::new(),
episode_history: StatefulTable::default(),
episode_releases: StatefulTable::default(),
episode_details_tabs: TabState::new(vec![
TabRoute {
title: "Details",
route: ActiveSonarrBlock::EpisodeDetails.into(),
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
contextual_help: None,
},
TabRoute {
title: "History",
route: ActiveSonarrBlock::EpisodeHistory.into(),
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
contextual_help: None,
},
TabRoute {
title: "File",
route: ActiveSonarrBlock::EpisodeFile.into(),
help: build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES),
contextual_help: None,
},
TabRoute {
title: "Manual Search",
route: ActiveSonarrBlock::ManualEpisodeSearch.into(),
help: build_context_clue_string(&MANUAL_EPISODE_SEARCH_CONTEXT_CLUES),
contextual_help: Some(build_context_clue_string(
&MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
)),
},
]),
}
}
} }
#[derive(Default)]
pub struct SeasonDetailsModal { pub struct SeasonDetailsModal {
pub episodes: StatefulTable<Episode>, pub episodes: StatefulTable<Episode>,
pub episode_details_modal: Option<EpisodeDetailsModal>, pub episode_details_modal: Option<EpisodeDetailsModal>,
pub season_releases: StatefulTable<SonarrRelease>, pub season_releases: StatefulTable<SonarrRelease>,
pub season_details_tabs: TabState,
}
impl Default for SeasonDetailsModal {
fn default() -> SeasonDetailsModal {
SeasonDetailsModal {
episodes: StatefulTable::default(),
episode_details_modal: None,
season_releases: StatefulTable::default(),
season_details_tabs: TabState::new(vec![
TabRoute {
title: "Episodes",
route: ActiveSonarrBlock::SeasonDetails.into(),
help: String::new(),
contextual_help: Some(build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES)),
},
TabRoute {
title: "Manual Search",
route: ActiveSonarrBlock::ManualSeasonSearch.into(),
help: String::new(),
contextual_help: Some(build_context_clue_string(
&MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
)),
},
]),
}
}
} }
+136 -1
View File
@@ -5,7 +5,16 @@ mod tests {
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::models::servarr_data::sonarr::modals::EditSeriesModal; use crate::app::context_clues::build_context_clue_string;
use crate::app::sonarr::sonarr_context_clues::{
EPISODE_DETAILS_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
SEASON_DETAILS_CONTEXT_CLUES,
};
use crate::models::servarr_data::sonarr::modals::{
EditSeriesModal, EpisodeDetailsModal, SeasonDetailsModal,
};
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::servarr_models::{Indexer, IndexerField}; use crate::models::servarr_models::{Indexer, IndexerField};
use crate::models::{ use crate::models::{
servarr_data::sonarr::{modals::AddSeriesModal, sonarr_data::SonarrData}, servarr_data::sonarr::{modals::AddSeriesModal, sonarr_data::SonarrData},
@@ -221,4 +230,130 @@ mod tests {
assert_eq!(edit_series_modal.monitored, Some(true)); assert_eq!(edit_series_modal.monitored, Some(true));
assert_eq!(edit_series_modal.use_season_folders, Some(true)); assert_eq!(edit_series_modal.use_season_folders, Some(true));
} }
#[test]
fn test_episode_details_modal_default() {
let episode_details_modal = EpisodeDetailsModal::default();
assert!(episode_details_modal.episode_details.is_empty());
assert!(episode_details_modal.file_details.is_empty());
assert!(episode_details_modal.audio_details.is_empty());
assert!(episode_details_modal.video_details.is_empty());
assert!(episode_details_modal.episode_history.is_empty());
assert!(episode_details_modal.episode_releases.is_empty());
assert_eq!(episode_details_modal.episode_details_tabs.tabs.len(), 4);
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[0].title,
"Details"
);
assert_eq!(
episode_details_modal.episode_details_tabs.tabs[0].route,
ActiveSonarrBlock::EpisodeDetails.into()
);
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[0].help,
build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES)
);
assert!(episode_details_modal.episode_details_tabs.tabs[0]
.contextual_help
.is_none());
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[1].title,
"History"
);
assert_eq!(
episode_details_modal.episode_details_tabs.tabs[1].route,
ActiveSonarrBlock::EpisodeHistory.into()
);
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[1].help,
build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES)
);
assert!(episode_details_modal.episode_details_tabs.tabs[1]
.contextual_help
.is_none());
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[2].title,
"File"
);
assert_eq!(
episode_details_modal.episode_details_tabs.tabs[2].route,
ActiveSonarrBlock::EpisodeFile.into()
);
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[2].help,
build_context_clue_string(&EPISODE_DETAILS_CONTEXT_CLUES)
);
assert!(episode_details_modal.episode_details_tabs.tabs[2]
.contextual_help
.is_none());
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[3].title,
"Manual Search"
);
assert_eq!(
episode_details_modal.episode_details_tabs.tabs[3].route,
ActiveSonarrBlock::ManualEpisodeSearch.into()
);
assert_str_eq!(
episode_details_modal.episode_details_tabs.tabs[3].help,
build_context_clue_string(&MANUAL_EPISODE_SEARCH_CONTEXT_CLUES)
);
assert_eq!(
episode_details_modal.episode_details_tabs.tabs[3].contextual_help,
Some(build_context_clue_string(
&MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES
))
);
}
#[test]
fn test_season_details_modal_default() {
let season_details_modal = SeasonDetailsModal::default();
assert!(season_details_modal.episodes.is_empty());
assert!(season_details_modal.episode_details_modal.is_none());
assert!(season_details_modal.season_releases.is_empty());
assert_eq!(season_details_modal.season_details_tabs.tabs.len(), 2);
assert_str_eq!(
season_details_modal.season_details_tabs.tabs[0].title,
"Episodes"
);
assert_eq!(
season_details_modal.season_details_tabs.tabs[0].route,
ActiveSonarrBlock::SeasonDetails.into()
);
assert!(season_details_modal.season_details_tabs.tabs[0]
.help
.is_empty());
assert_eq!(
season_details_modal.season_details_tabs.tabs[0].contextual_help,
Some(build_context_clue_string(&SEASON_DETAILS_CONTEXT_CLUES))
);
assert_str_eq!(
season_details_modal.season_details_tabs.tabs[1].title,
"Manual Search"
);
assert_eq!(
season_details_modal.season_details_tabs.tabs[1].route,
ActiveSonarrBlock::ManualSeasonSearch.into()
);
assert!(season_details_modal.season_details_tabs.tabs[1]
.help
.is_empty());
assert_eq!(
season_details_modal.season_details_tabs.tabs[1].contextual_help,
Some(build_context_clue_string(
&MANUAL_SEASON_SEARCH_CONTEXT_CLUES
))
);
}
} }
+24 -1
View File
@@ -8,7 +8,9 @@ use crate::{
build_context_clue_string, BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, build_context_clue_string, BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
}, },
sonarr::sonarr_context_clues::{HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES}, sonarr::sonarr_context_clues::{
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
},
}, },
models::{ models::{
servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem}, servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem},
@@ -30,6 +32,10 @@ use super::modals::{AddSeriesModal, EditSeriesModal, SeasonDetailsModal};
#[path = "sonarr_data_tests.rs"] #[path = "sonarr_data_tests.rs"]
mod sonarr_data_tests; mod sonarr_data_tests;
#[cfg(test)]
#[path = "sonarr_test_utils.rs"]
pub mod sonarr_test_utils;
pub struct SonarrData<'a> { pub struct SonarrData<'a> {
pub add_list_exclusion: bool, pub add_list_exclusion: bool,
pub add_searched_series: Option<StatefulTable<AddSeriesSearchResult>>, pub add_searched_series: Option<StatefulTable<AddSeriesSearchResult>>,
@@ -49,6 +55,7 @@ pub struct SonarrData<'a> {
pub indexer_test_error: Option<String>, pub indexer_test_error: Option<String>,
pub language_profiles_map: BiMap<i64, String>, pub language_profiles_map: BiMap<i64, String>,
pub logs: StatefulList<HorizontallyScrollableText>, pub logs: StatefulList<HorizontallyScrollableText>,
pub log_details: StatefulList<HorizontallyScrollableText>,
pub main_tabs: TabState, pub main_tabs: TabState,
pub prompt_confirm: bool, pub prompt_confirm: bool,
pub prompt_confirm_action: Option<SonarrEvent>, pub prompt_confirm_action: Option<SonarrEvent>,
@@ -60,6 +67,7 @@ pub struct SonarrData<'a> {
pub selected_block: BlockSelectionState<'a, ActiveSonarrBlock>, pub selected_block: BlockSelectionState<'a, ActiveSonarrBlock>,
pub series: StatefulTable<Series>, pub series: StatefulTable<Series>,
pub series_history: Option<StatefulTable<SonarrHistoryItem>>, pub series_history: Option<StatefulTable<SonarrHistoryItem>>,
pub series_info_tabs: TabState,
pub start_time: DateTime<Utc>, pub start_time: DateTime<Utc>,
pub tags_map: BiMap<i64, String>, pub tags_map: BiMap<i64, String>,
pub tasks: StatefulTable<SonarrTask>, pub tasks: StatefulTable<SonarrTask>,
@@ -95,6 +103,7 @@ impl<'a> Default for SonarrData<'a> {
indexer_test_all_results: None, indexer_test_all_results: None,
language_profiles_map: BiMap::new(), language_profiles_map: BiMap::new(),
logs: StatefulList::default(), logs: StatefulList::default(),
log_details: StatefulList::default(),
prompt_confirm: false, prompt_confirm: false,
prompt_confirm_action: None, prompt_confirm_action: None,
quality_profile_map: BiMap::new(), quality_profile_map: BiMap::new(),
@@ -154,6 +163,20 @@ impl<'a> Default for SonarrData<'a> {
contextual_help: Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)), contextual_help: Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)),
}, },
]), ]),
series_info_tabs: TabState::new(vec![
TabRoute {
title: "Seasons",
route: ActiveSonarrBlock::SeriesDetails.into(),
help: String::new(),
contextual_help: Some(build_context_clue_string(&SERIES_DETAILS_CONTEXT_CLUES)),
},
TabRoute {
title: "History",
route: ActiveSonarrBlock::SeriesHistory.into(),
help: String::new(),
contextual_help: Some(build_context_clue_string(&HISTORY_CONTEXT_CLUES)),
},
]),
} }
} }
} }
@@ -10,7 +10,9 @@ mod tests {
build_context_clue_string, BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, build_context_clue_string, BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
}, },
sonarr::sonarr_context_clues::{HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES}, sonarr::sonarr_context_clues::{
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
},
}, },
models::{ models::{
servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData}, servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData},
@@ -76,6 +78,7 @@ mod tests {
assert!(sonarr_data.indexer_test_all_results.is_none()); assert!(sonarr_data.indexer_test_all_results.is_none());
assert!(sonarr_data.language_profiles_map.is_empty()); assert!(sonarr_data.language_profiles_map.is_empty());
assert!(sonarr_data.logs.is_empty()); assert!(sonarr_data.logs.is_empty());
assert!(sonarr_data.log_details.is_empty());
assert!(!sonarr_data.prompt_confirm); assert!(!sonarr_data.prompt_confirm);
assert!(sonarr_data.prompt_confirm_action.is_none()); assert!(sonarr_data.prompt_confirm_action.is_none());
assert!(sonarr_data.quality_profile_map.is_empty()); assert!(sonarr_data.quality_profile_map.is_empty());
@@ -170,6 +173,30 @@ mod tests {
sonarr_data.main_tabs.tabs[6].contextual_help, sonarr_data.main_tabs.tabs[6].contextual_help,
Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES)) Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES))
); );
assert_eq!(sonarr_data.series_info_tabs.tabs.len(), 2);
assert_str_eq!(sonarr_data.series_info_tabs.tabs[0].title, "Seasons");
assert_eq!(
sonarr_data.series_info_tabs.tabs[0].route,
ActiveSonarrBlock::SeriesDetails.into()
);
assert!(sonarr_data.series_info_tabs.tabs[0].help.is_empty());
assert_eq!(
sonarr_data.series_info_tabs.tabs[0].contextual_help,
Some(build_context_clue_string(&SERIES_DETAILS_CONTEXT_CLUES))
);
assert_str_eq!(sonarr_data.series_info_tabs.tabs[1].title, "History");
assert_eq!(
sonarr_data.series_info_tabs.tabs[1].route,
ActiveSonarrBlock::SeriesHistory.into()
);
assert!(sonarr_data.series_info_tabs.tabs[1].help.is_empty());
assert_eq!(
sonarr_data.series_info_tabs.tabs[1].contextual_help,
Some(build_context_clue_string(&HISTORY_CONTEXT_CLUES))
);
} }
} }
@@ -0,0 +1,58 @@
#[cfg(test)]
pub mod utils {
use crate::models::{
servarr_data::sonarr::{
modals::{EpisodeDetailsModal, SeasonDetailsModal},
sonarr_data::SonarrData,
},
sonarr_models::{AddSeriesSearchResult, Episode, Season, SonarrHistoryItem, SonarrRelease},
stateful_table::StatefulTable,
HorizontallyScrollableText, ScrollableText,
};
pub fn create_test_sonarr_data<'a>() -> SonarrData<'a> {
let mut episode_details_modal = EpisodeDetailsModal {
episode_details: ScrollableText::with_string("test episode details".to_owned()),
..EpisodeDetailsModal::default()
};
episode_details_modal
.episode_history
.set_items(vec![SonarrHistoryItem::default()]);
episode_details_modal
.episode_releases
.set_items(vec![SonarrRelease::default()]);
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal
.episodes
.set_items(vec![Episode::default()]);
season_details_modal
.season_releases
.set_items(vec![SonarrRelease::default()]);
season_details_modal.episode_details_modal = Some(episode_details_modal);
let mut seasons = StatefulTable::default();
seasons.set_items(vec![Season::default()]);
let mut sonarr_data = SonarrData {
delete_series_files: true,
add_list_exclusion: true,
add_series_search: Some("test search".into()),
edit_root_folder: Some("test path".into()),
seasons,
season_details_modal: Some(season_details_modal),
add_searched_series: Some(StatefulTable::default()),
..SonarrData::default()
};
sonarr_data.series_info_tabs.index = 1;
sonarr_data
.add_searched_series
.as_mut()
.unwrap()
.set_items(vec![AddSeriesSearchResult::default()]);
sonarr_data
.log_details
.set_items(vec![HorizontallyScrollableText::default()]);
sonarr_data
}
}