feat: scaffold Readarr support across the state, app, handler, UI, and CLI layers

Promote Route::Readarr from a unit stub to a tuple variant and wire Readarr
through every layer, mirroring the Lidarr tree. Readarr appears as a fourth
servarr tab and its library view renders the Authors table.

Adds ReadarrData and its modals, the tick and context clue modules, the
top-level ReadarrHandler, ReadarrUi with the Authors library view, and an
empty ReadarrCommand behind ReadarrCliHandler. The default port is 8787.

Promoting the route variant compiles cleanly without any of the arms that
give it behaviour, so all six of those sites are wired explicitly here:
App::on_tick, handlers::handle_events, handle_prompt_toggle,
ServarrContextClueProvider, ui(), and verify_config_present_for_cli. Each was
checked by removing the arm and confirming its covering assertion then fails.

ReadarrEvent and ReadarrCommand are both declared empty. They gain variants
alongside the network calls and CLI commands that use them, which keeps each
later change to a single function.

Adding the tab changes the rendered tab bar, so 13 existing snapshots
regenerate. The only delta in each is that single tab bar line.

This scaffold adds 24 dead_code warnings, all inside the Readarr tree and
none outside it. They clear as the handlers, UI, network events, and CLI
commands that consume these items land: block arrays awaiting their accepts
implementations and BlockSelectionState seeding, modal and state fields
awaiting their handlers, the CLI handler fields awaiting an inhabited command
enum, and the top-level handler fields awaiting sub-handlers. The count must
strictly decrease from here, so a later increase is a defect rather than
transient noise. The 12 pre-existing collapsible_match warnings are unrelated
and untouched.
This commit is contained in:
Dark-Alex-17 committed 2026-08-20 15:28:49 -06:00
1 parent 4db9b92146
commit 29f043f5c7
64 files changed
+6418 -22

No files matched your search

+1
View File
@@ -3,6 +3,7 @@ use crate::models::Route;
pub mod lidarr;
pub mod modals;
pub mod radarr;
pub mod readarr;
pub mod sonarr;
#[cfg(test)]
+2
View File
@@ -0,0 +1,2 @@
pub mod modals;
pub mod readarr_data;
+271
View File
@@ -0,0 +1,271 @@
use super::readarr_data::{ActiveReadarrBlock, ReadarrData};
use crate::app::readarr::readarr_context_clues::{
BOOK_DETAILS_CONTEXT_CLUES, BOOK_HISTORY_CONTEXT_CLUES, MANUAL_BOOK_SEARCH_CONTEXT_CLUES,
};
use crate::models::readarr_models::{BookFile, Edition, ReadarrHistoryItem, ReadarrRelease};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::Indexer;
use crate::models::stateful_table::StatefulTable;
use crate::models::{
HorizontallyScrollableText, ScrollableText, TabRoute, TabState,
readarr_models::{MonitorType, NewItemMonitorType},
servarr_models::RootFolder,
stateful_list::StatefulList,
};
use strum::IntoEnumIterator;
#[cfg(test)]
#[path = "modals_tests.rs"]
mod modals_tests;
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct AddAuthorModal {
pub root_folder_list: StatefulList<RootFolder>,
pub monitor_list: StatefulList<MonitorType>,
pub monitor_new_items_list: StatefulList<NewItemMonitorType>,
pub quality_profile_list: StatefulList<String>,
pub metadata_profile_list: StatefulList<String>,
pub tags: HorizontallyScrollableText,
}
impl From<&ReadarrData<'_>> for AddAuthorModal {
fn from(readarr_data: &ReadarrData<'_>) -> AddAuthorModal {
let mut add_author_modal = AddAuthorModal::default();
add_author_modal
.monitor_list
.set_items(Vec::from_iter(MonitorType::iter()));
add_author_modal
.monitor_new_items_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
add_author_modal
.quality_profile_list
.set_items(readarr_data.sorted_quality_profile_names());
add_author_modal
.metadata_profile_list
.set_items(readarr_data.sorted_metadata_profile_names());
add_author_modal
.root_folder_list
.set_items(readarr_data.root_folders.items.to_vec());
add_author_modal
}
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct EditAuthorModal {
pub monitor_list: StatefulList<NewItemMonitorType>,
pub quality_profile_list: StatefulList<String>,
pub metadata_profile_list: StatefulList<String>,
pub monitored: Option<bool>,
pub path: HorizontallyScrollableText,
pub tags: HorizontallyScrollableText,
}
impl From<&ReadarrData<'_>> for EditAuthorModal {
fn from(readarr_data: &ReadarrData<'_>) -> EditAuthorModal {
let mut edit_author_modal = EditAuthorModal::default();
let author = readarr_data.authors.current_selection();
edit_author_modal
.monitor_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
edit_author_modal.path = author.path.clone().into();
edit_author_modal.tags = readarr_data.tag_ids_to_display(&author.tags).into();
edit_author_modal.monitored = Some(author.monitored);
let monitor_index = edit_author_modal
.monitor_list
.items
.iter()
.position(|m| *m == author.monitor_new_items);
edit_author_modal.monitor_list.state.select(monitor_index);
edit_author_modal
.quality_profile_list
.set_items(readarr_data.sorted_quality_profile_names());
let quality_profile_name = readarr_data
.quality_profile_map
.get_by_left(&author.quality_profile_id)
.unwrap();
let quality_profile_index = edit_author_modal
.quality_profile_list
.items
.iter()
.position(|profile| profile == quality_profile_name);
edit_author_modal
.quality_profile_list
.state
.select(quality_profile_index);
edit_author_modal
.metadata_profile_list
.set_items(readarr_data.sorted_metadata_profile_names());
let metadata_profile_name = readarr_data
.metadata_profile_map
.get_by_left(&author.metadata_profile_id)
.unwrap();
let metadata_profile_index = edit_author_modal
.metadata_profile_list
.items
.iter()
.position(|profile| profile == metadata_profile_name);
edit_author_modal
.metadata_profile_list
.state
.select(metadata_profile_index);
edit_author_modal
}
}
impl From<&ReadarrData<'_>> for EditIndexerModal {
fn from(readarr_data: &ReadarrData<'_>) -> EditIndexerModal {
let mut edit_indexer_modal = EditIndexerModal::default();
let Indexer {
name,
enable_rss,
enable_automatic_search,
enable_interactive_search,
tags,
fields,
priority,
..
} = readarr_data.indexers.current_selection();
let seed_ratio_field_option = fields
.as_ref()
.expect("indexer fields must exist")
.iter()
.find(|field| {
field.name.as_ref().expect("indexer field name must exist") == "seedCriteria.seedRatio"
});
let seed_ratio_value_option = if let Some(seed_ratio_field) = seed_ratio_field_option {
seed_ratio_field.value.clone()
} else {
None
};
edit_indexer_modal.name = name.clone().expect("indexer name must exist").into();
edit_indexer_modal.enable_rss = Some(*enable_rss);
edit_indexer_modal.enable_automatic_search = Some(*enable_automatic_search);
edit_indexer_modal.enable_interactive_search = Some(*enable_interactive_search);
edit_indexer_modal.priority = *priority;
edit_indexer_modal.url = fields
.as_ref()
.expect("indexer fields must exist")
.iter()
.find(|field| field.name.as_ref().expect("indexer field name must exist") == "baseUrl")
.expect("baseUrl field must exist")
.value
.clone()
.expect("baseUrl field value must exist")
.as_str()
.expect("baseUrl field value must be a string")
.into();
edit_indexer_modal.api_key = fields
.as_ref()
.expect("indexer fields must exist")
.iter()
.find(|field| field.name.as_ref().expect("indexer field name must exist") == "apiKey")
.expect("apiKey field must exist")
.value
.clone()
.expect("apiKey field value must exist")
.as_str()
.expect("apiKey field value must be a string")
.into();
if let Some(seed_ratio_value) = seed_ratio_value_option {
edit_indexer_modal.seed_ratio = seed_ratio_value
.as_f64()
.expect("Seed ratio value must be a valid f64")
.to_string()
.into();
}
edit_indexer_modal.tags = readarr_data.tag_ids_to_display(tags).into();
edit_indexer_modal
}
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct AddReadarrRootFolderModal {
pub name: HorizontallyScrollableText,
pub path: HorizontallyScrollableText,
pub monitor_list: StatefulList<MonitorType>,
pub monitor_new_items_list: StatefulList<NewItemMonitorType>,
pub quality_profile_list: StatefulList<String>,
pub metadata_profile_list: StatefulList<String>,
pub tags: HorizontallyScrollableText,
}
impl From<&ReadarrData<'_>> for AddReadarrRootFolderModal {
fn from(readarr_data: &ReadarrData<'_>) -> AddReadarrRootFolderModal {
let mut add_root_folder_modal = AddReadarrRootFolderModal::default();
add_root_folder_modal
.monitor_list
.set_items(Vec::from_iter(MonitorType::iter()));
add_root_folder_modal
.monitor_new_items_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
add_root_folder_modal
.quality_profile_list
.set_items(readarr_data.sorted_quality_profile_names());
add_root_folder_modal
.metadata_profile_list
.set_items(readarr_data.sorted_metadata_profile_names());
add_root_folder_modal
}
}
#[cfg_attr(test, derive(Debug))]
pub struct BookDetailsModal {
pub editions: StatefulTable<Edition>,
pub book_files: StatefulTable<BookFile>,
pub edition_details_modal: Option<EditionDetailsModal>,
pub book_history: StatefulTable<ReadarrHistoryItem>,
pub book_releases: StatefulTable<ReadarrRelease>,
pub book_details_tabs: TabState,
}
impl Default for BookDetailsModal {
fn default() -> BookDetailsModal {
BookDetailsModal {
editions: StatefulTable::default(),
edition_details_modal: None,
book_files: StatefulTable::default(),
book_releases: StatefulTable::default(),
book_history: StatefulTable::default(),
book_details_tabs: TabState::new(vec![
TabRoute {
title: "Editions".to_string(),
route: ActiveReadarrBlock::BookDetails.into(),
contextual_help: Some(&BOOK_DETAILS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "History".to_string(),
route: ActiveReadarrBlock::BookHistory.into(),
contextual_help: Some(&BOOK_HISTORY_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Manual Search".to_string(),
route: ActiveReadarrBlock::ManualBookSearch.into(),
contextual_help: Some(&MANUAL_BOOK_SEARCH_CONTEXT_CLUES),
config: None,
},
]),
}
}
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct EditionDetailsModal {
pub edition_details: ScrollableText,
}
@@ -0,0 +1,322 @@
#[cfg(test)]
mod tests {
use crate::app::readarr::readarr_context_clues::{
BOOK_DETAILS_CONTEXT_CLUES, BOOK_HISTORY_CONTEXT_CLUES, MANUAL_BOOK_SEARCH_CONTEXT_CLUES,
};
use crate::models::readarr_models::{Author, MonitorType, NewItemMonitorType};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::readarr::modals::{
AddAuthorModal, AddReadarrRootFolderModal, BookDetailsModal, EditAuthorModal,
EditionDetailsModal,
};
use crate::models::servarr_data::readarr::readarr_data::{ActiveReadarrBlock, ReadarrData};
use crate::models::servarr_models::{Indexer, IndexerField, RootFolder};
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use serde_json::{Number, Value};
#[test]
fn test_add_author_modal_from_readarr_data() {
let mut readarr_data = ReadarrData {
quality_profile_map: BiMap::from_iter([
(2i64, "Lossless".to_owned()),
(1i64, "Standard".to_owned()),
]),
metadata_profile_map: BiMap::from_iter([
(2i64, "None".to_owned()),
(1i64, "Standard".to_owned()),
]),
..ReadarrData::default()
};
let root_folder_1 = RootFolder {
id: 1,
path: "/nfs".to_owned(),
accessible: true,
free_space: 219902325555200,
unmapped_folders: None,
};
readarr_data.root_folders.set_items(vec![
root_folder_1.clone(),
RootFolder {
id: 2,
path: "/nfs2".to_owned(),
accessible: true,
free_space: 21990232555520,
unmapped_folders: None,
},
]);
let add_author_modal = AddAuthorModal::from(&readarr_data);
assert_eq!(
*add_author_modal.monitor_list.current_selection(),
MonitorType::default()
);
assert_eq!(
*add_author_modal.monitor_new_items_list.current_selection(),
NewItemMonitorType::default()
);
assert_str_eq!(
add_author_modal.quality_profile_list.current_selection(),
"Standard"
);
assert_str_eq!(
add_author_modal.metadata_profile_list.current_selection(),
"Standard"
);
assert_eq!(
add_author_modal.root_folder_list.current_selection(),
&root_folder_1
);
assert_is_empty!(add_author_modal.tags.text);
}
#[test]
fn test_edit_author_modal_from_readarr_data() {
let mut readarr_data = ReadarrData {
quality_profile_map: BiMap::from_iter([
(1i64, "HD - 1080p".to_owned()),
(2i64, "Any".to_owned()),
]),
metadata_profile_map: BiMap::from_iter([
(1i64, "Standard".to_owned()),
(2i64, "None".to_owned()),
]),
tags_map: BiMap::from_iter([(1i64, "usenet".to_owned())]),
..ReadarrData::default()
};
let author = Author {
id: 1,
monitored: true,
monitor_new_items: NewItemMonitorType::New,
quality_profile_id: 1,
metadata_profile_id: 1,
path: "/nfs/books/test_author".to_owned(),
tags: vec![Number::from(1)],
..Author::default()
};
readarr_data.authors.set_items(vec![author]);
let edit_author_modal = EditAuthorModal::from(&readarr_data);
assert_some_eq_x!(&edit_author_modal.monitored, &true);
assert_eq!(
*edit_author_modal.monitor_list.current_selection(),
NewItemMonitorType::New
);
assert_str_eq!(
edit_author_modal.quality_profile_list.current_selection(),
"HD - 1080p"
);
assert_str_eq!(
edit_author_modal.metadata_profile_list.current_selection(),
"Standard"
);
assert_str_eq!(edit_author_modal.path.text, "/nfs/books/test_author");
assert_str_eq!(edit_author_modal.tags.text, "usenet");
}
#[rstest]
fn test_edit_indexer_modal_from_readarr_data(#[values(true, false)] seed_ratio_present: bool) {
let mut readarr_data = ReadarrData {
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
..ReadarrData::default()
};
let mut fields = vec![
IndexerField {
name: Some("baseUrl".to_owned()),
value: Some(Value::String("https://test.com".to_owned())),
},
IndexerField {
name: Some("apiKey".to_owned()),
value: Some(Value::String("1234".to_owned())),
},
];
if seed_ratio_present {
fields.push(IndexerField {
name: Some("seedCriteria.seedRatio".to_owned()),
value: Some(Value::from(1.2f64)),
});
}
let indexer = Indexer {
name: Some("Test".to_owned()),
enable_rss: true,
enable_automatic_search: true,
enable_interactive_search: true,
tags: vec![Number::from(1), Number::from(2)],
fields: Some(fields),
priority: 1,
..Indexer::default()
};
readarr_data.indexers.set_items(vec![indexer]);
let edit_indexer_modal = EditIndexerModal::from(&readarr_data);
assert_str_eq!(edit_indexer_modal.name.text, "Test");
assert_some_eq_x!(&edit_indexer_modal.enable_rss, &true);
assert_some_eq_x!(&edit_indexer_modal.enable_automatic_search, &true);
assert_some_eq_x!(&edit_indexer_modal.enable_interactive_search, &true);
assert_eq!(edit_indexer_modal.priority, 1);
assert_str_eq!(edit_indexer_modal.url.text, "https://test.com");
assert_str_eq!(edit_indexer_modal.api_key.text, "1234");
if seed_ratio_present {
assert_str_eq!(edit_indexer_modal.seed_ratio.text, "1.2");
} else {
assert_is_empty!(edit_indexer_modal.seed_ratio.text);
}
}
#[test]
fn test_edit_indexer_modal_from_readarr_data_seed_ratio_value_is_none() {
let mut readarr_data = ReadarrData {
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
..ReadarrData::default()
};
let fields = vec![
IndexerField {
name: Some("baseUrl".to_owned()),
value: Some(Value::String("https://test.com".to_owned())),
},
IndexerField {
name: Some("apiKey".to_owned()),
value: Some(Value::String("1234".to_owned())),
},
IndexerField {
name: Some("seedCriteria.seedRatio".to_owned()),
value: None,
},
];
let indexer = Indexer {
name: Some("Test".to_owned()),
enable_rss: true,
enable_automatic_search: true,
enable_interactive_search: true,
tags: vec![Number::from(1), Number::from(2)],
fields: Some(fields),
priority: 1,
..Indexer::default()
};
readarr_data.indexers.set_items(vec![indexer]);
let edit_indexer_modal = EditIndexerModal::from(&readarr_data);
assert_str_eq!(edit_indexer_modal.name.text, "Test");
assert_some_eq_x!(&edit_indexer_modal.enable_rss, &true);
assert_some_eq_x!(&edit_indexer_modal.enable_automatic_search, &true);
assert_some_eq_x!(&edit_indexer_modal.enable_interactive_search, &true);
assert_eq!(edit_indexer_modal.priority, 1);
assert_str_eq!(edit_indexer_modal.url.text, "https://test.com");
assert_str_eq!(edit_indexer_modal.api_key.text, "1234");
assert_is_empty!(edit_indexer_modal.seed_ratio.text);
}
#[test]
fn test_add_readarr_root_folder_modal_from_readarr_data() {
let readarr_data = ReadarrData {
quality_profile_map: BiMap::from_iter([
(2i64, "Lossless".to_owned()),
(1i64, "Standard".to_owned()),
]),
metadata_profile_map: BiMap::from_iter([
(2i64, "None".to_owned()),
(1i64, "Standard".to_owned()),
]),
..ReadarrData::default()
};
let add_root_folder_modal = AddReadarrRootFolderModal::from(&readarr_data);
assert_eq!(
*add_root_folder_modal.monitor_list.current_selection(),
MonitorType::default()
);
assert_eq!(
*add_root_folder_modal
.monitor_new_items_list
.current_selection(),
NewItemMonitorType::default()
);
assert_str_eq!(
add_root_folder_modal
.quality_profile_list
.current_selection(),
"Standard"
);
assert_str_eq!(
add_root_folder_modal
.metadata_profile_list
.current_selection(),
"Standard"
);
assert_is_empty!(add_root_folder_modal.name.text);
assert_is_empty!(add_root_folder_modal.path.text);
assert_is_empty!(add_root_folder_modal.tags.text);
}
#[test]
fn test_book_details_modal_default() {
let book_details_modal = BookDetailsModal::default();
assert_is_empty!(book_details_modal.editions);
assert_none!(book_details_modal.edition_details_modal);
assert_is_empty!(book_details_modal.book_files);
assert_is_empty!(book_details_modal.book_releases);
assert_is_empty!(book_details_modal.book_history);
assert_eq!(book_details_modal.book_details_tabs.tabs.len(), 3);
assert_str_eq!(
book_details_modal.book_details_tabs.tabs[0].title,
"Editions"
);
assert_eq!(
book_details_modal.book_details_tabs.tabs[0].route,
ActiveReadarrBlock::BookDetails.into()
);
assert_some_eq_x!(
&book_details_modal.book_details_tabs.tabs[0].contextual_help,
&BOOK_DETAILS_CONTEXT_CLUES
);
assert_none!(book_details_modal.book_details_tabs.tabs[0].config);
assert_str_eq!(
book_details_modal.book_details_tabs.tabs[1].title,
"History"
);
assert_eq!(
book_details_modal.book_details_tabs.tabs[1].route,
ActiveReadarrBlock::BookHistory.into()
);
assert_some_eq_x!(
&book_details_modal.book_details_tabs.tabs[1].contextual_help,
&BOOK_HISTORY_CONTEXT_CLUES
);
assert_none!(book_details_modal.book_details_tabs.tabs[1].config);
assert_str_eq!(
book_details_modal.book_details_tabs.tabs[2].title,
"Manual Search"
);
assert_eq!(
book_details_modal.book_details_tabs.tabs[2].route,
ActiveReadarrBlock::ManualBookSearch.into()
);
assert_some_eq_x!(
&book_details_modal.book_details_tabs.tabs[2].contextual_help,
&MANUAL_BOOK_SEARCH_CONTEXT_CLUES
);
assert_none!(book_details_modal.book_details_tabs.tabs[2].config);
}
#[test]
fn test_edition_details_modal_default() {
let edition_details_modal = EditionDetailsModal::default();
assert_is_empty!(edition_details_modal.edition_details);
}
}
@@ -0,0 +1,980 @@
use serde_json::Number;
use super::modals::{AddAuthorModal, AddReadarrRootFolderModal, BookDetailsModal, EditAuthorModal};
use crate::app::context_clues::{
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::readarr::readarr_context_clues::{
AUTHOR_DETAILS_CONTEXT_CLUES, AUTHOR_HISTORY_CONTEXT_CLUES, AUTHORS_CONTEXT_CLUES,
MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES,
};
use crate::models::readarr_models::{BlocklistItem, ReadarrRelease, ReadarrTask};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::{IndexerSettings, QueueEvent};
use crate::models::stateful_list::StatefulList;
use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, TabRoute, TabState,
readarr_models::{AddAuthorSearchResult, Author, Book, DownloadRecord, ReadarrHistoryItem},
servarr_data::modals::IndexerTestResultModalItem,
servarr_models::{DiskSpace, Indexer, RootFolder},
stateful_table::StatefulTable,
};
use crate::network::readarr_network::ReadarrEvent;
use bimap::BiMap;
use chrono::{DateTime, Utc};
use itertools::Itertools;
use strum::EnumIter;
#[cfg(test)]
use {
super::modals::EditionDetailsModal,
crate::models::readarr_models::{
AuthorStatus, BookFile, DownloadStatus, Edition, MonitorType, NewItemMonitorType,
ReadarrHistoryEventType, ReadarrTaskName,
},
crate::models::servarr_models::{IndexerField, Quality, QualityWrapper},
crate::models::stateful_table::SortOption,
crate::network::servarr_test_utils::{
diskspace, indexer_settings, indexer_test_result, queued_event,
},
crate::network::sonarr_network::sonarr_network_test_utils::test_utils::updates,
crate::sort_option,
serde_json::json,
strum::{Display, EnumString, IntoEnumIterator},
};
#[cfg(test)]
#[path = "readarr_data_tests.rs"]
mod readarr_data_tests;
pub struct ReadarrData<'a> {
pub add_author_modal: Option<AddAuthorModal>,
pub add_author_search: Option<HorizontallyScrollableText>,
pub add_import_list_exclusion: bool,
pub add_root_folder_modal: Option<AddReadarrRootFolderModal>,
pub add_searched_authors: Option<StatefulTable<AddAuthorSearchResult>>,
pub author_history: StatefulTable<ReadarrHistoryItem>,
pub author_info_tabs: TabState,
pub author_releases: StatefulTable<ReadarrRelease>,
pub authors: StatefulTable<Author>,
pub blocklist: StatefulTable<BlocklistItem>,
pub book_details_modal: Option<BookDetailsModal>,
pub books: StatefulTable<Book>,
pub delete_files: bool,
pub disk_space_vec: Vec<DiskSpace>,
pub downloads: StatefulTable<DownloadRecord>,
pub edit_author_modal: Option<EditAuthorModal>,
pub edit_indexer_modal: Option<EditIndexerModal>,
pub history: StatefulTable<ReadarrHistoryItem>,
pub indexer_settings: Option<IndexerSettings>,
pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>,
pub indexer_test_errors: Option<String>,
pub indexers: StatefulTable<Indexer>,
pub log_details: StatefulList<HorizontallyScrollableText>,
pub logs: StatefulList<HorizontallyScrollableText>,
pub main_tabs: TabState,
pub metadata_profile_map: BiMap<i64, String>,
pub prompt_confirm: bool,
pub prompt_confirm_action: Option<ReadarrEvent>,
pub quality_profile_map: BiMap<i64, String>,
pub queued_events: StatefulTable<QueueEvent>,
pub root_folders: StatefulTable<RootFolder>,
pub selected_block: BlockSelectionState<'a, ActiveReadarrBlock>,
pub start_time: DateTime<Utc>,
pub tags_map: BiMap<i64, String>,
pub tasks: StatefulTable<ReadarrTask>,
pub updates: ScrollableText,
pub version: String,
}
impl ReadarrData<'_> {
pub fn reset_delete_preferences(&mut self) {
self.delete_files = false;
self.add_import_list_exclusion = false;
}
pub fn reset_author_info_tabs(&mut self) {
self.books = StatefulTable::default();
self.author_releases = StatefulTable::default();
self.author_history = StatefulTable::default();
self.author_info_tabs.index = 0;
}
pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String {
tag_ids
.iter()
.filter_map(|id| {
let id = id.as_i64()?;
self.tags_map.get_by_left(&id).cloned()
})
.collect::<Vec<String>>()
.join(", ")
}
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
self
.quality_profile_map
.iter()
.sorted_by_key(|(id, _)| *id)
.map(|(_, name)| name)
.cloned()
.collect()
}
pub fn sorted_metadata_profile_names(&self) -> Vec<String> {
self
.metadata_profile_map
.iter()
.sorted_by_key(|(id, _)| *id)
.map(|(_, name)| name)
.cloned()
.collect()
}
}
impl<'a> Default for ReadarrData<'a> {
fn default() -> ReadarrData<'a> {
ReadarrData {
add_author_modal: None,
add_author_search: None,
add_import_list_exclusion: false,
add_root_folder_modal: None,
add_searched_authors: None,
author_history: StatefulTable::default(),
author_releases: StatefulTable::default(),
authors: StatefulTable::default(),
blocklist: StatefulTable::default(),
book_details_modal: None,
books: StatefulTable::default(),
delete_files: false,
disk_space_vec: Vec::new(),
downloads: StatefulTable::default(),
edit_author_modal: None,
edit_indexer_modal: None,
history: StatefulTable::default(),
indexer_settings: None,
indexer_test_all_results: None,
indexer_test_errors: None,
indexers: StatefulTable::default(),
log_details: StatefulList::default(),
logs: StatefulList::default(),
metadata_profile_map: BiMap::new(),
prompt_confirm: false,
prompt_confirm_action: None,
quality_profile_map: BiMap::new(),
queued_events: StatefulTable::default(),
root_folders: StatefulTable::default(),
selected_block: BlockSelectionState::default(),
start_time: DateTime::default(),
tags_map: BiMap::new(),
tasks: StatefulTable::default(),
updates: ScrollableText::default(),
version: String::new(),
main_tabs: TabState::new(vec![
TabRoute {
title: "Library".to_string(),
route: ActiveReadarrBlock::Authors.into(),
contextual_help: Some(&AUTHORS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Downloads".to_string(),
route: ActiveReadarrBlock::Downloads.into(),
contextual_help: Some(&DOWNLOADS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Blocklist".to_string(),
route: ActiveReadarrBlock::Blocklist.into(),
contextual_help: Some(&BLOCKLIST_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "History".to_string(),
route: ActiveReadarrBlock::History.into(),
contextual_help: Some(&HISTORY_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Root Folders".to_string(),
route: ActiveReadarrBlock::RootFolders.into(),
contextual_help: Some(&ROOT_FOLDERS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Indexers".to_string(),
route: ActiveReadarrBlock::Indexers.into(),
contextual_help: Some(&INDEXERS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "System".to_string(),
route: ActiveReadarrBlock::System.into(),
contextual_help: Some(&SYSTEM_CONTEXT_CLUES),
config: None,
},
]),
author_info_tabs: TabState::new(vec![
TabRoute {
title: "Books".to_string(),
route: ActiveReadarrBlock::AuthorDetails.into(),
contextual_help: Some(&AUTHOR_DETAILS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "History".to_string(),
route: ActiveReadarrBlock::AuthorHistory.into(),
contextual_help: Some(&AUTHOR_HISTORY_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Author Search".to_string(),
route: ActiveReadarrBlock::ManualAuthorSearch.into(),
contextual_help: Some(&MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES),
config: None,
},
]),
}
}
}
#[cfg(test)]
impl ReadarrData<'_> {
pub fn test_default_fully_populated() -> Self {
let quality_profile_map = BiMap::from_iter([(1i64, "Standard".to_owned())]);
let metadata_profile_map = BiMap::from_iter([(1i64, "Standard".to_owned())]);
let tags_map = BiMap::from_iter([(1i64, "alex".to_owned())]);
let root_folder = RootFolder {
id: 1,
path: "/nfs".to_owned(),
accessible: true,
free_space: 219902325555200,
unmapped_folders: None,
};
let quality = QualityWrapper {
quality: Quality {
name: "EPUB".to_owned(),
},
};
let date = DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T21:29:16Z").unwrap());
let author = Author {
id: 1,
author_name: "Test Author".into(),
foreign_author_id: "test-foreign-id".to_owned(),
status: AuthorStatus::Continuing,
overview: Some("some interesting description of the author".to_owned()),
path: "/nfs/books/Test Author".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
monitored: true,
monitor_new_items: NewItemMonitorType::All,
genres: vec!["science fiction".to_owned()],
tags: vec![Number::from(1)],
..Author::default()
};
let book = Book {
id: 1,
title: "Test Book".into(),
author_id: 1,
foreign_book_id: "test-foreign-book-id".to_owned(),
monitored: true,
any_edition_ok: true,
page_count: Some(288),
release_date: Some(date),
..Book::default()
};
let readarr_history_item = ReadarrHistoryItem {
id: 1,
author_id: 1,
book_id: 1,
source_title: "Test Source Title".into(),
quality: quality.clone(),
date,
event_type: ReadarrHistoryEventType::Grabbed,
..ReadarrHistoryItem::default()
};
let torrent_release = ReadarrRelease {
guid: "1234".to_owned(),
protocol: "torrent".to_owned(),
age: 1,
title: "Test Torrent Release".into(),
author_name: Some("Test Author".to_owned()),
book_title: Some("Test Book".to_owned()),
indexer: "kickass torrents".to_owned(),
indexer_id: 1,
size: 1234,
rejected: true,
rejections: Some(vec!["Unknown quality profile".to_owned()]),
seeders: Some(Number::from(2)),
leechers: Some(Number::from(1)),
quality: quality.clone(),
};
let usenet_release = ReadarrRelease {
guid: "5678".to_owned(),
protocol: "usenet".to_owned(),
title: "Test Usenet Release".into(),
indexer: "DrunkenSlug".to_owned(),
indexer_id: 2,
seeders: None,
leechers: None,
..torrent_release.clone()
};
let blocklist_item = BlocklistItem {
id: 1,
author_id: 1,
book_ids: Some(vec![Number::from(1)]),
source_title: "Test Source Title".to_owned(),
quality: quality.clone(),
date,
protocol: "usenet".to_owned(),
indexer: "DrunkenSlug".to_owned(),
message: "test message".to_owned(),
author: author.clone(),
};
let download_record = DownloadRecord {
title: "Test Download Title".to_owned(),
status: DownloadStatus::Downloading,
id: 1,
book_id: Some(Number::from(1)),
author_id: Some(Number::from(1)),
size: 3543348019f64,
sizeleft: 1771674009f64,
output_path: Some("/nfs/books/Test Author".into()),
indexer: "kickass torrents".to_owned(),
download_client: Some("transmission".to_owned()),
};
let indexer = Indexer {
id: 1,
name: Some("Test Indexer".to_owned()),
implementation: Some("Torznab".to_owned()),
implementation_name: Some("Torznab".to_owned()),
config_contract: Some("TorznabSettings".to_owned()),
supports_rss: true,
supports_search: true,
enable_rss: true,
enable_automatic_search: true,
enable_interactive_search: true,
protocol: "torrent".to_owned(),
priority: 25,
download_client_id: 0,
tags: vec![Number::from(1)],
fields: Some(vec![
IndexerField {
name: Some("baseUrl".to_owned()),
value: Some(json!("https://test.com")),
},
IndexerField {
name: Some("apiKey".to_owned()),
value: Some(json!("")),
},
IndexerField {
name: Some("seedCriteria.seedRatio".to_owned()),
value: Some(json!("1.2")),
},
]),
};
let add_author_search_result = AddAuthorSearchResult {
foreign_author_id: "test-foreign-id".to_owned(),
author_name: "Test Author".into(),
status: AuthorStatus::Continuing,
overview: Some("some interesting description of the author".to_owned()),
genres: vec!["science fiction".to_owned()],
..AddAuthorSearchResult::default()
};
let task = ReadarrTask {
name: "Backup".to_owned(),
task_name: ReadarrTaskName::Backup,
interval: 60,
last_execution: date,
last_duration: "00:00:00.5111547".to_owned(),
next_execution: DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T22:29:16Z").unwrap()),
};
let log_line = "2025-12-16 16:40:59 UTC|INFO|ImportListSyncService|No list items to process";
let mut add_author_modal = AddAuthorModal {
tags: "usenet, testing".into(),
..AddAuthorModal::default()
};
add_author_modal
.monitor_list
.set_items(Vec::from_iter(MonitorType::iter()));
add_author_modal
.monitor_new_items_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
add_author_modal
.metadata_profile_list
.set_items(vec!["Standard".to_owned()]);
add_author_modal
.quality_profile_list
.set_items(vec!["Standard".to_owned()]);
add_author_modal
.root_folder_list
.set_items(vec![root_folder.clone()]);
let mut edit_author_modal = EditAuthorModal {
monitored: Some(true),
path: "/nfs/books".into(),
tags: "alex".into(),
..EditAuthorModal::default()
};
edit_author_modal
.monitor_list
.set_items(NewItemMonitorType::iter().collect());
edit_author_modal
.quality_profile_list
.set_items(vec!["Standard".to_owned()]);
edit_author_modal
.metadata_profile_list
.set_items(vec!["Standard".to_owned()]);
let mut add_root_folder_modal = AddReadarrRootFolderModal {
name: "Test Root Folder".into(),
path: "/nfs/books".into(),
tags: "test".into(),
..AddReadarrRootFolderModal::default()
};
add_root_folder_modal
.monitor_list
.set_items(Vec::from_iter(MonitorType::iter()));
add_root_folder_modal
.monitor_new_items_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
add_root_folder_modal
.quality_profile_list
.set_items(vec!["Standard".to_owned()]);
add_root_folder_modal
.metadata_profile_list
.set_items(vec!["Standard".to_owned()]);
let edition_details_modal = EditionDetailsModal {
edition_details: ScrollableText::with_string("Some details".to_owned()),
};
let mut book_details_modal = BookDetailsModal {
edition_details_modal: Some(edition_details_modal),
..BookDetailsModal::default()
};
book_details_modal.editions.set_items(vec![Edition {
id: 1,
book_id: 1,
foreign_edition_id: "test-foreign-edition-id".to_owned(),
monitored: true,
is_ebook: true,
title: "Test Edition".to_owned(),
page_count: Some(288),
release_date: Some(date),
..Edition::default()
}]);
book_details_modal.editions.search = Some("edition search".into());
book_details_modal.book_files.set_items(vec![BookFile {
id: 1,
author_id: 1,
book_id: 1,
path: "/nfs/books/Test Author/Test Book.epub".to_owned(),
size: 3543348019,
date_added: date,
quality: quality.clone(),
media_info: None,
quality_cutoff_not_met: false,
}]);
book_details_modal
.book_history
.set_items(vec![readarr_history_item.clone()]);
book_details_modal.book_history.search = Some("book history search".into());
book_details_modal.book_history.filter = Some("book history filter".into());
book_details_modal
.book_history
.sorting(vec![sort_option!(id)]);
book_details_modal
.book_releases
.set_items(vec![torrent_release.clone(), usenet_release.clone()]);
book_details_modal
.book_releases
.sorting(vec![sort_option!(indexer_id)]);
let edit_indexer_modal = EditIndexerModal {
name: "DrunkenSlug".into(),
enable_rss: Some(true),
enable_automatic_search: Some(true),
enable_interactive_search: Some(true),
url: "http://127.0.0.1:9696/1/".into(),
api_key: "someApiKey".into(),
seed_ratio: "ratio".into(),
tags: "25".into(),
priority: 1,
};
let mut indexer_test_all_results = StatefulTable::default();
indexer_test_all_results.set_items(vec![indexer_test_result()]);
let mut readarr_data = ReadarrData {
add_author_modal: Some(add_author_modal),
add_root_folder_modal: Some(add_root_folder_modal),
book_details_modal: Some(book_details_modal),
delete_files: true,
disk_space_vec: vec![diskspace()],
edit_author_modal: Some(edit_author_modal),
edit_indexer_modal: Some(edit_indexer_modal),
indexer_settings: Some(indexer_settings()),
indexer_test_all_results: Some(indexer_test_all_results),
indexer_test_errors: Some("error".to_string()),
metadata_profile_map,
quality_profile_map,
start_time: date,
tags_map,
updates: updates(),
version: "1.2.3.4".to_owned(),
..ReadarrData::default()
};
readarr_data
.author_history
.set_items(vec![readarr_history_item.clone()]);
readarr_data.author_history.sorting(vec![sort_option!(id)]);
readarr_data.author_history.search = Some("author history search".into());
readarr_data.author_history.filter = Some("author history filter".into());
readarr_data
.author_releases
.set_items(vec![torrent_release, usenet_release]);
readarr_data
.author_releases
.sorting(vec![sort_option!(indexer_id)]);
readarr_data.authors.set_items(vec![author]);
readarr_data.authors.sorting(vec![SortOption {
name: "Name",
cmp_fn: Some(|a: &Author, b: &Author| a.author_name.text.cmp(&b.author_name.text)),
}]);
readarr_data.authors.search = Some("author search".into());
readarr_data.authors.filter = Some("author filter".into());
readarr_data.blocklist.set_items(vec![blocklist_item]);
readarr_data.blocklist.sorting(vec![sort_option!(id)]);
readarr_data.books.set_items(vec![book]);
readarr_data.books.search = Some("book search".into());
readarr_data.downloads.set_items(vec![download_record]);
readarr_data.history.set_items(vec![readarr_history_item]);
readarr_data.history.sorting(vec![SortOption {
name: "Date",
cmp_fn: Some(|a: &ReadarrHistoryItem, b: &ReadarrHistoryItem| a.date.cmp(&b.date)),
}]);
readarr_data.history.search = Some("test search".into());
readarr_data.history.filter = Some("test filter".into());
readarr_data.indexers.set_items(vec![indexer]);
readarr_data.root_folders.set_items(vec![root_folder]);
readarr_data.queued_events.set_items(vec![queued_event()]);
readarr_data.add_author_search = Some("Test Author".into());
let mut add_searched_authors = StatefulTable::default();
add_searched_authors.set_items(vec![add_author_search_result]);
readarr_data.add_searched_authors = Some(add_searched_authors);
readarr_data.logs.set_items(vec![log_line.into()]);
readarr_data.log_details.set_items(vec![log_line.into()]);
readarr_data.tasks.set_items(vec![task]);
readarr_data
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, EnumIter)]
#[cfg_attr(test, derive(Display, EnumString))]
pub enum ActiveReadarrBlock {
#[default]
Authors,
AddAuthorAlreadyInLibrary,
AddAuthorConfirmPrompt,
AddAuthorEmptySearchResults,
AddAuthorPrompt,
AddAuthorSearchInput,
AddAuthorSearchResults,
AddAuthorSelectMetadataProfile,
AddAuthorSelectMonitor,
AddAuthorSelectMonitorNewItems,
AddAuthorSelectQualityProfile,
AddAuthorSelectRootFolder,
AddAuthorTagsInput,
AddRootFolderConfirmPrompt,
AddRootFolderNameInput,
AddRootFolderPathInput,
AddRootFolderPrompt,
AddRootFolderSelectMetadataProfile,
AddRootFolderSelectMonitor,
AddRootFolderSelectMonitorNewItems,
AddRootFolderSelectQualityProfile,
AddRootFolderTagsInput,
AllIndexerSettingsPrompt,
AuthorDetails,
AuthorHistory,
AuthorHistoryDetails,
AuthorHistorySortPrompt,
AuthorsSortPrompt,
AutomaticallySearchAuthorPrompt,
AutomaticallySearchBookPrompt,
Blocklist,
BlocklistClearAllItemsPrompt,
BlocklistItemDetails,
BlocklistSortPrompt,
BookDetails,
BookEditionDetails,
BookFileInfo,
BookHistory,
BookHistoryDetails,
BookHistorySortPrompt,
DeleteAuthorConfirmPrompt,
DeleteAuthorPrompt,
DeleteAuthorToggleAddListExclusion,
DeleteAuthorToggleDeleteFile,
DeleteBlocklistItemPrompt,
DeleteBookConfirmPrompt,
DeleteBookFilePrompt,
DeleteBookPrompt,
DeleteBookToggleAddListExclusion,
DeleteBookToggleDeleteFile,
DeleteDownloadPrompt,
DeleteIndexerPrompt,
DeleteRootFolderPrompt,
Downloads,
EditAuthorConfirmPrompt,
EditAuthorPathInput,
EditAuthorPrompt,
EditAuthorSelectMetadataProfile,
EditAuthorSelectMonitorNewItems,
EditAuthorSelectQualityProfile,
EditAuthorTagsInput,
EditAuthorToggleMonitored,
EditIndexerApiKeyInput,
EditIndexerConfirmPrompt,
EditIndexerNameInput,
EditIndexerPriorityInput,
EditIndexerPrompt,
EditIndexerSeedRatioInput,
EditIndexerTagsInput,
EditIndexerToggleEnableAutomaticSearch,
EditIndexerToggleEnableInteractiveSearch,
EditIndexerToggleEnableRss,
EditIndexerUrlInput,
FilterAuthorHistory,
FilterAuthorHistoryError,
FilterAuthors,
FilterAuthorsError,
FilterBookHistory,
FilterBookHistoryError,
FilterHistory,
FilterHistoryError,
History,
HistoryItemDetails,
HistorySortPrompt,
Indexers,
IndexerSettingsConfirmPrompt,
IndexerSettingsMaximumSizeInput,
IndexerSettingsMinimumAgeInput,
IndexerSettingsRetentionInput,
IndexerSettingsRssSyncIntervalInput,
ManualAuthorSearch,
ManualAuthorSearchConfirmPrompt,
ManualAuthorSearchSortPrompt,
ManualBookSearch,
ManualBookSearchConfirmPrompt,
ManualBookSearchSortPrompt,
RootFolders,
SearchAuthorHistory,
SearchAuthorHistoryError,
SearchAuthors,
SearchAuthorsError,
SearchBookHistory,
SearchBookHistoryError,
SearchBooks,
SearchBooksError,
SearchEditions,
SearchEditionsError,
SearchHistory,
SearchHistoryError,
System,
SystemLogs,
SystemQueuedEvents,
SystemTasks,
SystemTaskStartConfirmPrompt,
SystemUpdates,
TestAllIndexers,
TestIndexer,
UpdateAllAuthorsPrompt,
UpdateAndScanAuthorPrompt,
UpdateDownloadsPrompt,
}
pub static LIBRARY_BLOCKS: [ActiveReadarrBlock; 7] = [
ActiveReadarrBlock::Authors,
ActiveReadarrBlock::AuthorsSortPrompt,
ActiveReadarrBlock::FilterAuthors,
ActiveReadarrBlock::FilterAuthorsError,
ActiveReadarrBlock::SearchAuthors,
ActiveReadarrBlock::SearchAuthorsError,
ActiveReadarrBlock::UpdateAllAuthorsPrompt,
];
pub static AUTHOR_DETAILS_BLOCKS: [ActiveReadarrBlock; 15] = [
ActiveReadarrBlock::AuthorDetails,
ActiveReadarrBlock::AuthorHistory,
ActiveReadarrBlock::AuthorHistoryDetails,
ActiveReadarrBlock::AuthorHistorySortPrompt,
ActiveReadarrBlock::AutomaticallySearchAuthorPrompt,
ActiveReadarrBlock::FilterAuthorHistory,
ActiveReadarrBlock::FilterAuthorHistoryError,
ActiveReadarrBlock::ManualAuthorSearch,
ActiveReadarrBlock::ManualAuthorSearchConfirmPrompt,
ActiveReadarrBlock::ManualAuthorSearchSortPrompt,
ActiveReadarrBlock::SearchBooks,
ActiveReadarrBlock::SearchBooksError,
ActiveReadarrBlock::SearchAuthorHistory,
ActiveReadarrBlock::SearchAuthorHistoryError,
ActiveReadarrBlock::UpdateAndScanAuthorPrompt,
];
pub static BOOK_DETAILS_BLOCKS: [ActiveReadarrBlock; 16] = [
ActiveReadarrBlock::BookDetails,
ActiveReadarrBlock::BookHistory,
ActiveReadarrBlock::BookHistoryDetails,
ActiveReadarrBlock::BookHistorySortPrompt,
ActiveReadarrBlock::AutomaticallySearchBookPrompt,
ActiveReadarrBlock::DeleteBookFilePrompt,
ActiveReadarrBlock::FilterBookHistory,
ActiveReadarrBlock::FilterBookHistoryError,
ActiveReadarrBlock::BookFileInfo,
ActiveReadarrBlock::ManualBookSearch,
ActiveReadarrBlock::ManualBookSearchConfirmPrompt,
ActiveReadarrBlock::ManualBookSearchSortPrompt,
ActiveReadarrBlock::SearchBookHistory,
ActiveReadarrBlock::SearchBookHistoryError,
ActiveReadarrBlock::SearchEditions,
ActiveReadarrBlock::SearchEditionsError,
];
pub static EDITION_DETAILS_BLOCKS: [ActiveReadarrBlock; 1] =
[ActiveReadarrBlock::BookEditionDetails];
pub static ADD_AUTHOR_BLOCKS: [ActiveReadarrBlock; 12] = [
ActiveReadarrBlock::AddAuthorAlreadyInLibrary,
ActiveReadarrBlock::AddAuthorConfirmPrompt,
ActiveReadarrBlock::AddAuthorEmptySearchResults,
ActiveReadarrBlock::AddAuthorPrompt,
ActiveReadarrBlock::AddAuthorSearchInput,
ActiveReadarrBlock::AddAuthorSearchResults,
ActiveReadarrBlock::AddAuthorSelectMetadataProfile,
ActiveReadarrBlock::AddAuthorSelectMonitor,
ActiveReadarrBlock::AddAuthorSelectMonitorNewItems,
ActiveReadarrBlock::AddAuthorSelectQualityProfile,
ActiveReadarrBlock::AddAuthorSelectRootFolder,
ActiveReadarrBlock::AddAuthorTagsInput,
];
pub const ADD_AUTHOR_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[ActiveReadarrBlock::AddAuthorSelectRootFolder],
&[ActiveReadarrBlock::AddAuthorSelectMonitor],
&[ActiveReadarrBlock::AddAuthorSelectMonitorNewItems],
&[ActiveReadarrBlock::AddAuthorSelectQualityProfile],
&[ActiveReadarrBlock::AddAuthorSelectMetadataProfile],
&[ActiveReadarrBlock::AddAuthorTagsInput],
&[ActiveReadarrBlock::AddAuthorConfirmPrompt],
];
pub static DELETE_AUTHOR_BLOCKS: [ActiveReadarrBlock; 4] = [
ActiveReadarrBlock::DeleteAuthorPrompt,
ActiveReadarrBlock::DeleteAuthorConfirmPrompt,
ActiveReadarrBlock::DeleteAuthorToggleDeleteFile,
ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion,
];
pub const DELETE_AUTHOR_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[ActiveReadarrBlock::DeleteAuthorToggleDeleteFile],
&[ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion],
&[ActiveReadarrBlock::DeleteAuthorConfirmPrompt],
];
pub static DELETE_BOOK_BLOCKS: [ActiveReadarrBlock; 4] = [
ActiveReadarrBlock::DeleteBookPrompt,
ActiveReadarrBlock::DeleteBookConfirmPrompt,
ActiveReadarrBlock::DeleteBookToggleDeleteFile,
ActiveReadarrBlock::DeleteBookToggleAddListExclusion,
];
pub const DELETE_BOOK_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[ActiveReadarrBlock::DeleteBookToggleDeleteFile],
&[ActiveReadarrBlock::DeleteBookToggleAddListExclusion],
&[ActiveReadarrBlock::DeleteBookConfirmPrompt],
];
pub static EDIT_AUTHOR_BLOCKS: [ActiveReadarrBlock; 8] = [
ActiveReadarrBlock::EditAuthorPrompt,
ActiveReadarrBlock::EditAuthorConfirmPrompt,
ActiveReadarrBlock::EditAuthorPathInput,
ActiveReadarrBlock::EditAuthorSelectMetadataProfile,
ActiveReadarrBlock::EditAuthorSelectMonitorNewItems,
ActiveReadarrBlock::EditAuthorSelectQualityProfile,
ActiveReadarrBlock::EditAuthorTagsInput,
ActiveReadarrBlock::EditAuthorToggleMonitored,
];
pub const EDIT_AUTHOR_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[ActiveReadarrBlock::EditAuthorToggleMonitored],
&[ActiveReadarrBlock::EditAuthorSelectMonitorNewItems],
&[ActiveReadarrBlock::EditAuthorSelectQualityProfile],
&[ActiveReadarrBlock::EditAuthorSelectMetadataProfile],
&[ActiveReadarrBlock::EditAuthorPathInput],
&[ActiveReadarrBlock::EditAuthorTagsInput],
&[ActiveReadarrBlock::EditAuthorConfirmPrompt],
];
pub static ADD_ROOT_FOLDER_BLOCKS: [ActiveReadarrBlock; 9] = [
ActiveReadarrBlock::AddRootFolderPrompt,
ActiveReadarrBlock::AddRootFolderConfirmPrompt,
ActiveReadarrBlock::AddRootFolderNameInput,
ActiveReadarrBlock::AddRootFolderPathInput,
ActiveReadarrBlock::AddRootFolderSelectMonitor,
ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems,
ActiveReadarrBlock::AddRootFolderSelectQualityProfile,
ActiveReadarrBlock::AddRootFolderSelectMetadataProfile,
ActiveReadarrBlock::AddRootFolderTagsInput,
];
pub const ADD_ROOT_FOLDER_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[ActiveReadarrBlock::AddRootFolderNameInput],
&[ActiveReadarrBlock::AddRootFolderPathInput],
&[ActiveReadarrBlock::AddRootFolderSelectMonitor],
&[ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems],
&[ActiveReadarrBlock::AddRootFolderSelectQualityProfile],
&[ActiveReadarrBlock::AddRootFolderSelectMetadataProfile],
&[ActiveReadarrBlock::AddRootFolderTagsInput],
&[ActiveReadarrBlock::AddRootFolderConfirmPrompt],
];
pub static ROOT_FOLDERS_BLOCKS: [ActiveReadarrBlock; 2] = [
ActiveReadarrBlock::RootFolders,
ActiveReadarrBlock::DeleteRootFolderPrompt,
];
pub static BLOCKLIST_BLOCKS: [ActiveReadarrBlock; 5] = [
ActiveReadarrBlock::Blocklist,
ActiveReadarrBlock::BlocklistItemDetails,
ActiveReadarrBlock::DeleteBlocklistItemPrompt,
ActiveReadarrBlock::BlocklistClearAllItemsPrompt,
ActiveReadarrBlock::BlocklistSortPrompt,
];
pub static DOWNLOADS_BLOCKS: [ActiveReadarrBlock; 3] = [
ActiveReadarrBlock::Downloads,
ActiveReadarrBlock::DeleteDownloadPrompt,
ActiveReadarrBlock::UpdateDownloadsPrompt,
];
pub static HISTORY_BLOCKS: [ActiveReadarrBlock; 7] = [
ActiveReadarrBlock::History,
ActiveReadarrBlock::HistoryItemDetails,
ActiveReadarrBlock::HistorySortPrompt,
ActiveReadarrBlock::SearchHistory,
ActiveReadarrBlock::SearchHistoryError,
ActiveReadarrBlock::FilterHistory,
ActiveReadarrBlock::FilterHistoryError,
];
pub static EDIT_INDEXER_BLOCKS: [ActiveReadarrBlock; 11] = [
ActiveReadarrBlock::EditIndexerPrompt,
ActiveReadarrBlock::EditIndexerConfirmPrompt,
ActiveReadarrBlock::EditIndexerApiKeyInput,
ActiveReadarrBlock::EditIndexerNameInput,
ActiveReadarrBlock::EditIndexerSeedRatioInput,
ActiveReadarrBlock::EditIndexerToggleEnableRss,
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveReadarrBlock::EditIndexerPriorityInput,
ActiveReadarrBlock::EditIndexerUrlInput,
ActiveReadarrBlock::EditIndexerTagsInput,
];
pub const EDIT_INDEXER_TORRENT_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[
ActiveReadarrBlock::EditIndexerNameInput,
ActiveReadarrBlock::EditIndexerUrlInput,
],
&[
ActiveReadarrBlock::EditIndexerToggleEnableRss,
ActiveReadarrBlock::EditIndexerApiKeyInput,
],
&[
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveReadarrBlock::EditIndexerSeedRatioInput,
],
&[
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveReadarrBlock::EditIndexerTagsInput,
],
&[
ActiveReadarrBlock::EditIndexerPriorityInput,
ActiveReadarrBlock::EditIndexerConfirmPrompt,
],
&[
ActiveReadarrBlock::EditIndexerConfirmPrompt,
ActiveReadarrBlock::EditIndexerConfirmPrompt,
],
];
pub const EDIT_INDEXER_NZB_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[
ActiveReadarrBlock::EditIndexerNameInput,
ActiveReadarrBlock::EditIndexerUrlInput,
],
&[
ActiveReadarrBlock::EditIndexerToggleEnableRss,
ActiveReadarrBlock::EditIndexerApiKeyInput,
],
&[
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveReadarrBlock::EditIndexerTagsInput,
],
&[
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveReadarrBlock::EditIndexerPriorityInput,
],
&[
ActiveReadarrBlock::EditIndexerConfirmPrompt,
ActiveReadarrBlock::EditIndexerConfirmPrompt,
],
];
pub static INDEXER_SETTINGS_BLOCKS: [ActiveReadarrBlock; 6] = [
ActiveReadarrBlock::AllIndexerSettingsPrompt,
ActiveReadarrBlock::IndexerSettingsConfirmPrompt,
ActiveReadarrBlock::IndexerSettingsMaximumSizeInput,
ActiveReadarrBlock::IndexerSettingsMinimumAgeInput,
ActiveReadarrBlock::IndexerSettingsRetentionInput,
ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput,
];
pub const INDEXER_SETTINGS_SELECTION_BLOCKS: &[&[ActiveReadarrBlock]] = &[
&[ActiveReadarrBlock::IndexerSettingsMinimumAgeInput],
&[ActiveReadarrBlock::IndexerSettingsRetentionInput],
&[ActiveReadarrBlock::IndexerSettingsMaximumSizeInput],
&[ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput],
&[ActiveReadarrBlock::IndexerSettingsConfirmPrompt],
];
pub static INDEXERS_BLOCKS: [ActiveReadarrBlock; 3] = [
ActiveReadarrBlock::Indexers,
ActiveReadarrBlock::DeleteIndexerPrompt,
ActiveReadarrBlock::TestIndexer,
];
pub static SYSTEM_DETAILS_BLOCKS: [ActiveReadarrBlock; 5] = [
ActiveReadarrBlock::SystemLogs,
ActiveReadarrBlock::SystemQueuedEvents,
ActiveReadarrBlock::SystemTasks,
ActiveReadarrBlock::SystemTaskStartConfirmPrompt,
ActiveReadarrBlock::SystemUpdates,
];
impl From<ActiveReadarrBlock> for Route {
fn from(active_readarr_block: ActiveReadarrBlock) -> Route {
Route::Readarr(active_readarr_block, None)
}
}
impl From<(ActiveReadarrBlock, Option<ActiveReadarrBlock>)> for Route {
fn from(value: (ActiveReadarrBlock, Option<ActiveReadarrBlock>)) -> Route {
Route::Readarr(value.0, value.1)
}
}
@@ -0,0 +1,789 @@
#[cfg(test)]
mod tests {
use crate::app::context_clues::{
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::readarr::readarr_context_clues::{
AUTHOR_DETAILS_CONTEXT_CLUES, AUTHOR_HISTORY_CONTEXT_CLUES, AUTHORS_CONTEXT_CLUES,
MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES,
};
use crate::models::readarr_models::{Book, ReadarrHistoryItem, ReadarrRelease};
use crate::models::servarr_data::readarr::readarr_data::{
ADD_AUTHOR_BLOCKS, ADD_AUTHOR_SELECTION_BLOCKS, ADD_ROOT_FOLDER_BLOCKS,
ADD_ROOT_FOLDER_SELECTION_BLOCKS, AUTHOR_DETAILS_BLOCKS, ActiveReadarrBlock, BLOCKLIST_BLOCKS,
BOOK_DETAILS_BLOCKS, DELETE_AUTHOR_BLOCKS, DELETE_AUTHOR_SELECTION_BLOCKS, DELETE_BOOK_BLOCKS,
DELETE_BOOK_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_AUTHOR_BLOCKS,
EDIT_AUTHOR_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDITION_DETAILS_BLOCKS, HISTORY_BLOCKS,
INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, LIBRARY_BLOCKS,
ROOT_FOLDERS_BLOCKS, ReadarrData, SYSTEM_DETAILS_BLOCKS,
};
use crate::models::{BlockSelectionState, Route};
use bimap::BiMap;
use chrono::{DateTime, Utc};
use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::Number;
#[test]
fn test_from_active_readarr_block_to_route() {
assert_eq!(
Route::from(ActiveReadarrBlock::Authors),
Route::Readarr(ActiveReadarrBlock::Authors, None)
);
}
#[test]
fn test_from_tuple_to_route_with_context() {
assert_eq!(
Route::from((
ActiveReadarrBlock::Authors,
Some(ActiveReadarrBlock::Authors)
)),
Route::Readarr(
ActiveReadarrBlock::Authors,
Some(ActiveReadarrBlock::Authors),
)
);
}
#[test]
fn test_reset_delete_preferences() {
let mut readarr_data = ReadarrData {
delete_files: true,
add_import_list_exclusion: true,
..ReadarrData::default()
};
readarr_data.reset_delete_preferences();
assert!(!readarr_data.delete_files);
assert!(!readarr_data.add_import_list_exclusion);
}
#[test]
fn test_reset_author_info_tabs() {
let mut readarr_data = ReadarrData::default();
readarr_data.books.set_items(vec![Book::default()]);
readarr_data
.author_releases
.set_items(vec![ReadarrRelease::default()]);
readarr_data
.author_history
.set_items(vec![ReadarrHistoryItem::default()]);
readarr_data.author_info_tabs.index = 1;
readarr_data.reset_author_info_tabs();
assert_is_empty!(readarr_data.books);
assert_is_empty!(readarr_data.author_releases);
assert_is_empty!(readarr_data.author_history);
assert_eq!(readarr_data.author_info_tabs.index, 0);
}
#[test]
fn test_tag_ids_to_display() {
let mut tags_map = BiMap::new();
tags_map.insert(3, "test 3".to_owned());
tags_map.insert(2, "test 2".to_owned());
tags_map.insert(1, "test 1".to_owned());
let readarr_data = ReadarrData {
tags_map,
..ReadarrData::default()
};
assert_str_eq!(
readarr_data.tag_ids_to_display(&[Number::from(1), Number::from(2)]),
"test 1, test 2"
);
}
#[test]
fn test_sorted_quality_profile_names() {
let mut quality_profile_map = BiMap::new();
quality_profile_map.insert(3, "test 1".to_owned());
quality_profile_map.insert(2, "test 2".to_owned());
quality_profile_map.insert(1, "test 3".to_owned());
let readarr_data = ReadarrData {
quality_profile_map,
..ReadarrData::default()
};
let expected_quality_profile_vec = vec![
"test 3".to_owned(),
"test 2".to_owned(),
"test 1".to_owned(),
];
assert_iter_eq!(
readarr_data.sorted_quality_profile_names(),
expected_quality_profile_vec
);
}
#[test]
fn test_sorted_metadata_profile_names() {
let mut metadata_profile_map = BiMap::new();
metadata_profile_map.insert(3, "test 1".to_owned());
metadata_profile_map.insert(2, "test 2".to_owned());
metadata_profile_map.insert(1, "test 3".to_owned());
let readarr_data = ReadarrData {
metadata_profile_map,
..ReadarrData::default()
};
let expected_metadata_profile_vec = vec![
"test 3".to_owned(),
"test 2".to_owned(),
"test 1".to_owned(),
];
assert_iter_eq!(
readarr_data.sorted_metadata_profile_names(),
expected_metadata_profile_vec
);
}
#[test]
fn test_readarr_data_default() {
let readarr_data = ReadarrData::default();
assert_none!(readarr_data.add_author_modal);
assert_none!(readarr_data.add_author_search);
assert!(!readarr_data.add_import_list_exclusion);
assert_none!(readarr_data.add_root_folder_modal);
assert_none!(readarr_data.add_searched_authors);
assert_is_empty!(readarr_data.author_history);
assert_is_empty!(readarr_data.author_releases);
assert_is_empty!(readarr_data.authors);
assert_is_empty!(readarr_data.blocklist);
assert_none!(readarr_data.book_details_modal);
assert_is_empty!(readarr_data.books);
assert!(!readarr_data.delete_files);
assert_is_empty!(readarr_data.disk_space_vec);
assert_is_empty!(readarr_data.downloads);
assert_none!(readarr_data.edit_author_modal);
assert_none!(readarr_data.edit_indexer_modal);
assert_is_empty!(readarr_data.history);
assert_none!(readarr_data.indexer_settings);
assert_none!(readarr_data.indexer_test_all_results);
assert_none!(readarr_data.indexer_test_errors);
assert_is_empty!(readarr_data.indexers);
assert_is_empty!(readarr_data.log_details);
assert_is_empty!(readarr_data.logs);
assert_is_empty!(readarr_data.metadata_profile_map);
assert!(!readarr_data.prompt_confirm);
assert_none!(readarr_data.prompt_confirm_action);
assert_is_empty!(readarr_data.quality_profile_map);
assert_is_empty!(readarr_data.queued_events);
assert_is_empty!(readarr_data.root_folders);
assert_eq!(readarr_data.selected_block, BlockSelectionState::default());
assert_eq!(readarr_data.start_time, <DateTime<Utc>>::default());
assert_is_empty!(readarr_data.tags_map);
assert_is_empty!(readarr_data.tasks);
assert_is_empty!(readarr_data.updates);
assert_is_empty!(readarr_data.version);
assert_eq!(readarr_data.main_tabs.tabs.len(), 7);
assert_str_eq!(readarr_data.main_tabs.tabs[0].title, "Library");
assert_eq!(
readarr_data.main_tabs.tabs[0].route,
ActiveReadarrBlock::Authors.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[0].contextual_help,
&AUTHORS_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[0].config);
assert_str_eq!(readarr_data.main_tabs.tabs[1].title, "Downloads");
assert_eq!(
readarr_data.main_tabs.tabs[1].route,
ActiveReadarrBlock::Downloads.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[1].contextual_help,
&DOWNLOADS_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[1].config);
assert_str_eq!(readarr_data.main_tabs.tabs[2].title, "Blocklist");
assert_eq!(
readarr_data.main_tabs.tabs[2].route,
ActiveReadarrBlock::Blocklist.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[2].contextual_help,
&BLOCKLIST_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[2].config);
assert_str_eq!(readarr_data.main_tabs.tabs[3].title, "History");
assert_eq!(
readarr_data.main_tabs.tabs[3].route,
ActiveReadarrBlock::History.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[3].contextual_help,
&HISTORY_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[3].config);
assert_str_eq!(readarr_data.main_tabs.tabs[4].title, "Root Folders");
assert_eq!(
readarr_data.main_tabs.tabs[4].route,
ActiveReadarrBlock::RootFolders.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[4].contextual_help,
&ROOT_FOLDERS_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[4].config);
assert_str_eq!(readarr_data.main_tabs.tabs[5].title, "Indexers");
assert_eq!(
readarr_data.main_tabs.tabs[5].route,
ActiveReadarrBlock::Indexers.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[5].contextual_help,
&INDEXERS_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[5].config);
assert_str_eq!(readarr_data.main_tabs.tabs[6].title, "System");
assert_eq!(
readarr_data.main_tabs.tabs[6].route,
ActiveReadarrBlock::System.into()
);
assert_some_eq_x!(
&readarr_data.main_tabs.tabs[6].contextual_help,
&SYSTEM_CONTEXT_CLUES
);
assert_none!(readarr_data.main_tabs.tabs[6].config);
assert_eq!(readarr_data.author_info_tabs.tabs.len(), 3);
assert_str_eq!(readarr_data.author_info_tabs.tabs[0].title, "Books");
assert_eq!(
readarr_data.author_info_tabs.tabs[0].route,
ActiveReadarrBlock::AuthorDetails.into()
);
assert_some_eq_x!(
&readarr_data.author_info_tabs.tabs[0].contextual_help,
&AUTHOR_DETAILS_CONTEXT_CLUES
);
assert_none!(readarr_data.author_info_tabs.tabs[0].config);
assert_str_eq!(readarr_data.author_info_tabs.tabs[1].title, "History");
assert_eq!(
readarr_data.author_info_tabs.tabs[1].route,
ActiveReadarrBlock::AuthorHistory.into()
);
assert_some_eq_x!(
&readarr_data.author_info_tabs.tabs[1].contextual_help,
&AUTHOR_HISTORY_CONTEXT_CLUES
);
assert_none!(readarr_data.author_info_tabs.tabs[1].config);
assert_str_eq!(readarr_data.author_info_tabs.tabs[2].title, "Author Search");
assert_eq!(
readarr_data.author_info_tabs.tabs[2].route,
ActiveReadarrBlock::ManualAuthorSearch.into()
);
assert_some_eq_x!(
&readarr_data.author_info_tabs.tabs[2].contextual_help,
&MANUAL_AUTHOR_SEARCH_CONTEXT_CLUES
);
assert_none!(readarr_data.author_info_tabs.tabs[2].config);
}
#[test]
fn test_library_blocks_contains_expected_blocks() {
assert_eq!(LIBRARY_BLOCKS.len(), 7);
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::Authors));
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::AuthorsSortPrompt));
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthors));
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthorsError));
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthors));
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthorsError));
assert!(LIBRARY_BLOCKS.contains(&ActiveReadarrBlock::UpdateAllAuthorsPrompt));
}
#[test]
fn test_author_details_blocks_contains_expected_blocks() {
assert_eq!(AUTHOR_DETAILS_BLOCKS.len(), 15);
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorDetails));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorHistory));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorHistoryDetails));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AuthorHistorySortPrompt));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AutomaticallySearchAuthorPrompt));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthorHistory));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterAuthorHistoryError));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualAuthorSearch));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualAuthorSearchConfirmPrompt));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualAuthorSearchSortPrompt));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBooks));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBooksError));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthorHistory));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchAuthorHistoryError));
assert!(AUTHOR_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::UpdateAndScanAuthorPrompt));
}
#[test]
fn test_book_details_blocks_contents() {
assert_eq!(BOOK_DETAILS_BLOCKS.len(), 16);
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookDetails));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookHistory));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookHistoryDetails));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookHistorySortPrompt));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::AutomaticallySearchBookPrompt));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookFilePrompt));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterBookHistory));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::FilterBookHistoryError));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookFileInfo));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualBookSearch));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualBookSearchConfirmPrompt));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::ManualBookSearchSortPrompt));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBookHistory));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchBookHistoryError));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchEditions));
assert!(BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SearchEditionsError));
}
#[test]
fn test_book_details_blocks_excludes_book_edition_details() {
assert!(!BOOK_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookEditionDetails));
}
#[test]
fn test_edition_details_blocks_contents() {
assert_eq!(EDITION_DETAILS_BLOCKS.len(), 1);
assert!(EDITION_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::BookEditionDetails));
}
#[test]
fn test_blocklist_blocks_contents() {
assert_eq!(BLOCKLIST_BLOCKS.len(), 5);
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::Blocklist));
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::BlocklistItemDetails));
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::DeleteBlocklistItemPrompt));
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::BlocklistClearAllItemsPrompt));
assert!(BLOCKLIST_BLOCKS.contains(&ActiveReadarrBlock::BlocklistSortPrompt));
}
#[test]
fn test_downloads_blocks_contains_expected_blocks() {
assert_eq!(DOWNLOADS_BLOCKS.len(), 3);
assert!(DOWNLOADS_BLOCKS.contains(&ActiveReadarrBlock::Downloads));
assert!(DOWNLOADS_BLOCKS.contains(&ActiveReadarrBlock::DeleteDownloadPrompt));
assert!(DOWNLOADS_BLOCKS.contains(&ActiveReadarrBlock::UpdateDownloadsPrompt));
}
#[test]
fn test_history_blocks_contains_expected_blocks() {
assert_eq!(HISTORY_BLOCKS.len(), 7);
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::History));
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::HistoryItemDetails));
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::HistorySortPrompt));
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::SearchHistory));
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::SearchHistoryError));
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::FilterHistory));
assert!(HISTORY_BLOCKS.contains(&ActiveReadarrBlock::FilterHistoryError));
}
#[test]
fn test_add_author_blocks_contents() {
assert_eq!(ADD_AUTHOR_BLOCKS.len(), 12);
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorAlreadyInLibrary));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorConfirmPrompt));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorEmptySearchResults));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorPrompt));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSearchInput));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSearchResults));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectMetadataProfile));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectMonitor));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectMonitorNewItems));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectQualityProfile));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorSelectRootFolder));
assert!(ADD_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::AddAuthorTagsInput));
}
#[test]
fn test_add_author_selection_blocks_ordering() {
let mut add_author_block_iter = ADD_AUTHOR_SELECTION_BLOCKS.iter();
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorSelectRootFolder]
);
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorSelectMonitor]
);
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorSelectMonitorNewItems]
);
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorSelectQualityProfile]
);
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorSelectMetadataProfile]
);
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorTagsInput]
);
assert_eq!(
add_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddAuthorConfirmPrompt]
);
assert_none!(add_author_block_iter.next());
}
#[test]
fn test_delete_author_blocks_contents() {
assert_eq!(DELETE_AUTHOR_BLOCKS.len(), 4);
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorPrompt));
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorConfirmPrompt));
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorToggleDeleteFile));
assert!(DELETE_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion));
}
#[test]
fn test_delete_author_selection_blocks_ordering() {
let mut delete_author_block_iter = DELETE_AUTHOR_SELECTION_BLOCKS.iter();
assert_eq!(
delete_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::DeleteAuthorToggleDeleteFile]
);
assert_eq!(
delete_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::DeleteAuthorToggleAddListExclusion]
);
assert_eq!(
delete_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::DeleteAuthorConfirmPrompt]
);
assert_none!(delete_author_block_iter.next());
}
#[test]
fn test_delete_book_blocks_contents() {
assert_eq!(DELETE_BOOK_BLOCKS.len(), 4);
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookPrompt));
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookConfirmPrompt));
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookToggleDeleteFile));
assert!(DELETE_BOOK_BLOCKS.contains(&ActiveReadarrBlock::DeleteBookToggleAddListExclusion));
}
#[test]
fn test_delete_book_selection_blocks_ordering() {
let mut delete_book_block_iter = DELETE_BOOK_SELECTION_BLOCKS.iter();
assert_eq!(
delete_book_block_iter.next().unwrap(),
&[ActiveReadarrBlock::DeleteBookToggleDeleteFile]
);
assert_eq!(
delete_book_block_iter.next().unwrap(),
&[ActiveReadarrBlock::DeleteBookToggleAddListExclusion]
);
assert_eq!(
delete_book_block_iter.next().unwrap(),
&[ActiveReadarrBlock::DeleteBookConfirmPrompt]
);
assert_none!(delete_book_block_iter.next());
}
#[test]
fn test_edit_author_blocks() {
assert_eq!(EDIT_AUTHOR_BLOCKS.len(), 8);
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorPrompt));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorConfirmPrompt));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorPathInput));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorSelectMetadataProfile));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorSelectMonitorNewItems));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorSelectQualityProfile));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorTagsInput));
assert!(EDIT_AUTHOR_BLOCKS.contains(&ActiveReadarrBlock::EditAuthorToggleMonitored));
}
#[test]
fn test_edit_author_selection_blocks_ordering() {
let mut edit_author_block_iter = EDIT_AUTHOR_SELECTION_BLOCKS.iter();
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorToggleMonitored]
);
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorSelectMonitorNewItems]
);
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorSelectQualityProfile]
);
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorSelectMetadataProfile]
);
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorPathInput]
);
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorTagsInput]
);
assert_eq!(
edit_author_block_iter.next().unwrap(),
&[ActiveReadarrBlock::EditAuthorConfirmPrompt]
);
assert_none!(edit_author_block_iter.next());
}
#[test]
fn test_root_folders_blocks_contents() {
assert_eq!(ROOT_FOLDERS_BLOCKS.len(), 2);
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveReadarrBlock::RootFolders));
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveReadarrBlock::DeleteRootFolderPrompt));
}
#[test]
fn test_add_root_folder_blocks_contents() {
assert_eq!(ADD_ROOT_FOLDER_BLOCKS.len(), 9);
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderPrompt));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderConfirmPrompt));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderNameInput));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderPathInput));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectMonitor));
assert!(
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems)
);
assert!(
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectQualityProfile)
);
assert!(
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderSelectMetadataProfile)
);
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveReadarrBlock::AddRootFolderTagsInput));
}
#[test]
fn test_add_root_folder_selection_blocks_ordering() {
let mut add_root_folder_block_iter = ADD_ROOT_FOLDER_SELECTION_BLOCKS.iter();
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderNameInput]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderPathInput]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderSelectMonitor]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderSelectMonitorNewItems]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderSelectQualityProfile]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderSelectMetadataProfile]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderTagsInput]
);
assert_eq!(
add_root_folder_block_iter.next().unwrap(),
&[ActiveReadarrBlock::AddRootFolderConfirmPrompt]
);
assert_none!(add_root_folder_block_iter.next());
}
#[test]
fn test_edit_indexer_blocks_contents() {
assert_eq!(EDIT_INDEXER_BLOCKS.len(), 11);
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerPrompt));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerConfirmPrompt));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerApiKeyInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerNameInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerSeedRatioInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerToggleEnableRss));
assert!(
EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch)
);
assert!(
EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch)
);
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerPriorityInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerUrlInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveReadarrBlock::EditIndexerTagsInput));
}
#[test]
fn test_edit_indexer_torrent_selection_blocks_ordering() {
let mut edit_indexer_block_iter = EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.iter();
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerNameInput,
ActiveReadarrBlock::EditIndexerUrlInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerToggleEnableRss,
ActiveReadarrBlock::EditIndexerApiKeyInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveReadarrBlock::EditIndexerSeedRatioInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveReadarrBlock::EditIndexerTagsInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerPriorityInput,
ActiveReadarrBlock::EditIndexerConfirmPrompt
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerConfirmPrompt,
ActiveReadarrBlock::EditIndexerConfirmPrompt
]
);
assert_none!(edit_indexer_block_iter.next());
}
#[test]
fn test_edit_indexer_nzb_selection_blocks_ordering() {
let mut edit_indexer_block_iter = EDIT_INDEXER_NZB_SELECTION_BLOCKS.iter();
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerNameInput,
ActiveReadarrBlock::EditIndexerUrlInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerToggleEnableRss,
ActiveReadarrBlock::EditIndexerApiKeyInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveReadarrBlock::EditIndexerTagsInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveReadarrBlock::EditIndexerPriorityInput
]
);
assert_eq!(
edit_indexer_block_iter.next().unwrap(),
&[
ActiveReadarrBlock::EditIndexerConfirmPrompt,
ActiveReadarrBlock::EditIndexerConfirmPrompt
]
);
assert_none!(edit_indexer_block_iter.next());
}
#[test]
fn test_indexer_settings_blocks_contents() {
assert_eq!(INDEXER_SETTINGS_BLOCKS.len(), 6);
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::AllIndexerSettingsPrompt));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsConfirmPrompt));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsMaximumSizeInput));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsMinimumAgeInput));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsRetentionInput));
assert!(
INDEXER_SETTINGS_BLOCKS.contains(&ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput)
);
}
#[test]
fn test_indexer_settings_selection_blocks_ordering() {
let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter();
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveReadarrBlock::IndexerSettingsMinimumAgeInput]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveReadarrBlock::IndexerSettingsRetentionInput]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveReadarrBlock::IndexerSettingsMaximumSizeInput]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveReadarrBlock::IndexerSettingsRssSyncIntervalInput]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveReadarrBlock::IndexerSettingsConfirmPrompt]
);
assert_none!(indexer_settings_block_iter.next());
}
#[test]
fn test_indexers_blocks_contents() {
assert_eq!(INDEXERS_BLOCKS.len(), 3);
assert!(INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::Indexers));
assert!(INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::DeleteIndexerPrompt));
assert!(INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::TestIndexer));
}
#[test]
fn test_indexers_blocks_excludes_test_all_indexers() {
assert!(!INDEXERS_BLOCKS.contains(&ActiveReadarrBlock::TestAllIndexers));
}
#[test]
fn test_system_details_blocks_contents() {
assert_eq!(SYSTEM_DETAILS_BLOCKS.len(), 5);
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemLogs));
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemQueuedEvents));
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemTasks));
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemTaskStartConfirmPrompt));
assert!(SYSTEM_DETAILS_BLOCKS.contains(&ActiveReadarrBlock::SystemUpdates));
}
}