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

+6
View File
@@ -10,6 +10,7 @@ use ratatui::widgets::Paragraph;
use ratatui::widgets::Tabs;
use ratatui::widgets::Wrap;
use ratatui::widgets::{Clear, Row};
use readarr_ui::ReadarrUi;
use sonarr_ui::SonarrUi;
use utils::layout_block;
@@ -32,6 +33,7 @@ use crate::ui::widgets::popup::{Popup, Size};
mod builtin_themes;
mod lidarr_ui;
mod radarr_ui;
mod readarr_ui;
mod sonarr_ui;
mod styles;
pub mod theme;
@@ -94,6 +96,10 @@ pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) {
LidarrUi::draw_context_row(f, app, context_area);
LidarrUi::draw(f, app, table_area);
}
route if ReadarrUi::accepts(route) => {
ReadarrUi::draw_context_row(f, app, context_area);
ReadarrUi::draw(f, app, table_area);
}
_ => (),
}
@@ -0,0 +1,235 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::models::readarr_models::{Author, AuthorStatistics, AuthorStatus};
use crate::models::servarr_data::readarr::readarr_data::{ActiveReadarrBlock, LIBRARY_BLOCKS};
use crate::ui::DrawUi;
use crate::ui::readarr_ui::library::{LibraryUi, decorate_author_row_with_style};
use crate::ui::styles::ManagarrStyle;
use pretty_assertions::assert_eq;
use ratatui::widgets::{Cell, Row};
#[test]
fn test_library_ui_accepts() {
for active_readarr_block in ActiveReadarrBlock::iter() {
if LIBRARY_BLOCKS.contains(&active_readarr_block) {
assert!(
LibraryUi::accepts(active_readarr_block.into()),
"{active_readarr_block} is not accepted by the LibraryUi"
);
} else {
assert!(
!LibraryUi::accepts(active_readarr_block.into()),
"{active_readarr_block} should not be accepted by LibraryUi"
);
}
}
}
#[test]
fn test_decorate_row_with_style_unmonitored() {
let author = Author::default();
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.unmonitored());
}
#[test]
fn test_decorate_row_with_style_downloaded_when_ended_and_all_books_present() {
let author = Author {
monitored: true,
status: AuthorStatus::Ended,
statistics: Some(AuthorStatistics {
book_file_count: 10,
total_book_count: 10,
..AuthorStatistics::default()
}),
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.downloaded());
}
#[test]
fn test_decorate_row_with_style_missing_when_ended_and_books_are_missing() {
let author = Author {
monitored: true,
status: AuthorStatus::Ended,
statistics: Some(AuthorStatistics {
book_file_count: 5,
total_book_count: 10,
..AuthorStatistics::default()
}),
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.missing());
}
#[test]
fn test_decorate_row_with_style_indeterminate_when_ended_and_no_statistics() {
let author = Author {
monitored: true,
status: AuthorStatus::Ended,
statistics: None,
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.indeterminate());
}
#[test]
fn test_decorate_row_with_style_indeterminate_when_ended_and_total_book_count_is_zero() {
let author = Author {
monitored: true,
status: AuthorStatus::Ended,
statistics: Some(AuthorStatistics {
book_file_count: 0,
total_book_count: 0,
..AuthorStatistics::default()
}),
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.missing());
}
#[test]
fn test_decorate_row_with_style_unreleased_when_continuing_and_all_books_present() {
let author = Author {
monitored: true,
status: AuthorStatus::Continuing,
statistics: Some(AuthorStatistics {
book_file_count: 10,
total_book_count: 10,
..AuthorStatistics::default()
}),
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.unreleased());
}
#[test]
fn test_decorate_row_with_style_missing_when_continuing_and_books_are_missing() {
let author = Author {
monitored: true,
status: AuthorStatus::Continuing,
statistics: Some(AuthorStatistics {
book_file_count: 5,
total_book_count: 10,
..AuthorStatistics::default()
}),
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.missing());
}
#[test]
fn test_decorate_row_with_style_indeterminate_when_continuing_and_no_statistics() {
let author = Author {
monitored: true,
status: AuthorStatus::Continuing,
statistics: None,
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.indeterminate());
}
#[test]
fn test_decorate_row_with_style_defaults_to_indeterminate_for_deleted_status() {
let author = Author {
monitored: true,
status: AuthorStatus::Deleted,
..Author::default()
};
let row = Row::new(vec![Cell::from("test".to_owned())]);
let style = decorate_author_row_with_style(&author, row.clone());
assert_eq!(style, row.indeterminate());
}
mod snapshot_tests {
use crate::app::App;
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
use rstest::rstest;
use crate::ui::DrawUi;
use crate::ui::readarr_ui::library::LibraryUi;
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
#[rstest]
fn test_library_ui_renders(
#[values(
ActiveReadarrBlock::Authors,
ActiveReadarrBlock::AuthorsSortPrompt,
ActiveReadarrBlock::SearchAuthors,
ActiveReadarrBlock::SearchAuthorsError,
ActiveReadarrBlock::FilterAuthors,
ActiveReadarrBlock::FilterAuthorsError
)]
active_readarr_block: ActiveReadarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_readarr_block.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
LibraryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("readarr_library_{active_readarr_block}"), output);
}
#[test]
fn test_library_ui_renders_loading() {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
app.push_navigation_stack(ActiveReadarrBlock::Authors.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
LibraryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_library_ui_renders_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveReadarrBlock::Authors.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
LibraryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
+176
View File
@@ -0,0 +1,176 @@
use ratatui::{
Frame,
layout::{Constraint, Rect},
widgets::{Cell, Row},
};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::utils::convert_to_gb;
use crate::{
app::App,
models::{
Route,
readarr_models::{Author, AuthorStatus},
servarr_data::readarr::readarr_data::{ActiveReadarrBlock, LIBRARY_BLOCKS},
},
ui::{
DrawUi,
styles::ManagarrStyle,
utils::{get_width_from_percentage, layout_block_top_border},
},
};
#[cfg(test)]
#[path = "library_ui_tests.rs"]
mod library_ui_tests;
pub(super) struct LibraryUi;
impl DrawUi for LibraryUi {
fn accepts(route: Route) -> bool {
if let Route::Readarr(active_readarr_block, _) = route {
return LIBRARY_BLOCKS.contains(&active_readarr_block);
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_library(f, app, area);
}
}
fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Readarr(active_readarr_block, _) = app.get_current_route() {
let current_selection = if !app.data.readarr_data.authors.items.is_empty() {
app.data.readarr_data.authors.current_selection().clone()
} else {
Author::default()
};
let quality_profile_map = &app.data.readarr_data.quality_profile_map;
let metadata_profile_map = &app.data.readarr_data.metadata_profile_map;
let tags_map = &app.data.readarr_data.tags_map;
let content = Some(&mut app.data.readarr_data.authors);
let authors_table_row_mapping = |author: &Author| {
author.author_name.scroll_left_or_reset(
get_width_from_percentage(area, 25),
*author == current_selection,
app.should_text_scroll,
);
let monitored = if author.monitored { "🏷" } else { "" };
let author_type = author.author_type.clone().unwrap_or_default();
let size = author
.statistics
.as_ref()
.map_or(0f64, |stats| convert_to_gb(stats.size_on_disk));
let quality_profile = quality_profile_map
.get_by_left(&author.quality_profile_id)
.cloned()
.unwrap_or_default();
let metadata_profile = metadata_profile_map
.get_by_left(&author.metadata_profile_id)
.cloned()
.unwrap_or_default();
let books = author
.statistics
.as_ref()
.map_or(0, |stats| stats.book_count);
let tags = author
.tags
.iter()
.filter_map(|tag_id| {
let id = tag_id.as_i64()?;
tags_map.get_by_left(&id).cloned()
})
.collect::<Vec<_>>()
.join(", ");
decorate_author_row_with_style(
author,
Row::new(vec![
Cell::from(author.author_name.to_string()),
Cell::from(author_type),
Cell::from(author.status.to_display_str()),
Cell::from(quality_profile),
Cell::from(metadata_profile),
Cell::from(books.to_string()),
Cell::from(format!("{size:.2} GB")),
Cell::from(monitored.to_owned()),
Cell::from(tags),
]),
)
};
let authors_table = ManagarrTable::new(content, authors_table_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_readarr_block == ActiveReadarrBlock::AuthorsSortPrompt)
.searching(active_readarr_block == ActiveReadarrBlock::SearchAuthors)
.filtering(active_readarr_block == ActiveReadarrBlock::FilterAuthors)
.search_produced_empty_results(active_readarr_block == ActiveReadarrBlock::SearchAuthorsError)
.filter_produced_empty_results(active_readarr_block == ActiveReadarrBlock::FilterAuthorsError)
.headers([
"Name",
"Type",
"Status",
"Quality Profile",
"Metadata Profile",
"Books",
"Size",
"Monitored",
"Tags",
])
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(9),
Constraint::Percentage(8),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(7),
Constraint::Percentage(8),
Constraint::Percentage(6),
Constraint::Percentage(11),
]);
if [
ActiveReadarrBlock::SearchAuthors,
ActiveReadarrBlock::FilterAuthors,
]
.contains(&active_readarr_block)
{
authors_table.show_cursor(f, area);
}
f.render_widget(authors_table, area);
}
}
fn decorate_author_row_with_style<'a>(author: &Author, row: Row<'a>) -> Row<'a> {
if !author.monitored {
return row.unmonitored();
}
match author.status {
AuthorStatus::Ended => {
if let Some(ref stats) = author.statistics {
return if stats.book_file_count == stats.total_book_count && stats.total_book_count > 0 {
row.downloaded()
} else {
row.missing()
};
}
row.indeterminate()
}
AuthorStatus::Continuing => {
if let Some(ref stats) = author.statistics {
return if stats.book_file_count == stats.total_book_count && stats.total_book_count > 0 {
row.unreleased()
} else {
row.missing()
};
}
row.indeterminate()
}
_ => row.indeterminate(),
}
}
@@ -0,0 +1,5 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,8 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading ...
@@ -0,0 +1,7 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
@@ -0,0 +1,42 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Name Type Status Quality Profile Metadata Profile Books Size Monitored Tags
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
╭───────────────────────────────╮
│Name │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────╯
@@ -0,0 +1,28 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
╭───────────────── Filter ──────────────────╮
│author filter │
╰─────────────────────────────────────────────╯
@@ -0,0 +1,31 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
╭─────────────── Error ───────────────╮
│The given filter produced empty results│
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,28 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
╭───────────────── Search ──────────────────╮
│author search │
╰─────────────────────────────────────────────╯
@@ -0,0 +1,31 @@
---
source: src/ui/readarr_ui/library/library_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags
=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex
╭─────────────── Error ───────────────╮
│ No items found matching search │
│ │
╰───────────────────────────────────────╯
+217
View File
@@ -0,0 +1,217 @@
#[cfg(test)]
use crate::ui::ui_test_utils::test_utils::Utc;
use chrono::Duration;
#[cfg(not(test))]
use chrono::Utc;
use library::LibraryUi;
use ratatui::{
Frame,
layout::{Constraint, Layout, Rect},
style::Stylize,
text::Text,
widgets::Paragraph,
};
use std::{cmp, iter};
use super::{
DrawUi, draw_tabs,
styles::ManagarrStyle,
utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
},
widgets::loading_block::LoadingBlock,
};
use crate::ui::utils::{extract_monitored_disk_space_vec, extract_monitored_root_folders};
use crate::{
app::App,
logos::READARR_LOGO,
models::{
Route,
readarr_models::DownloadRecord,
servarr_data::readarr::readarr_data::ReadarrData,
servarr_models::{DiskSpace, RootFolder},
},
utils::convert_to_gb,
};
mod library;
#[cfg(test)]
#[path = "readarr_ui_tests.rs"]
mod readarr_ui_tests;
pub(super) struct ReadarrUi;
impl DrawUi for ReadarrUi {
fn accepts(route: Route) -> bool {
matches!(route, Route::Readarr(_, _))
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let content_area = draw_tabs(f, area, "Authors", &app.data.readarr_data.main_tabs);
let route = app.get_current_route();
match route {
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
_ => (),
}
}
fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
let [main_area, logo_area] =
Layout::horizontal([Constraint::Fill(0), Constraint::Length(20)]).areas(area);
let [stats_area, downloads_area] =
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main_area);
draw_stats_context(f, app, stats_area);
draw_downloads_context(f, app, downloads_area);
draw_readarr_logo(f, logo_area);
}
}
fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
let block = title_block("Stats");
if !app.data.readarr_data.version.is_empty() {
f.render_widget(block, area);
let ReadarrData {
root_folders,
disk_space_vec,
start_time,
..
} = &app.data.readarr_data;
let monitored_disk_space_vec = extract_monitored_disk_space_vec(app, disk_space_vec.clone());
let monitored_root_folders = extract_monitored_root_folders(app, root_folders.items.clone());
let mut constraints = vec![
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(1),
];
constraints.append(
&mut iter::repeat_n(
Constraint::Length(1),
monitored_disk_space_vec.len() + monitored_root_folders.len() + 1,
)
.collect(),
);
let stat_item_areas = Layout::vertical(constraints).margin(1).split(area);
let version_paragraph = Paragraph::new(Text::from(format!(
"Readarr Version: {}",
app.data.readarr_data.version
)))
.block(borderless_block())
.bold();
let uptime = Utc::now() - start_time.to_owned();
let days = uptime.num_days();
let day_difference = uptime - Duration::days(days);
let hours = day_difference.num_hours();
let hour_difference = day_difference - Duration::hours(hours);
let minutes = hour_difference.num_minutes();
let seconds = (hour_difference - Duration::minutes(minutes)).num_seconds();
let uptime_paragraph = Paragraph::new(Text::from(format!(
"Uptime: {days}d {hours:0width$}:{minutes:0width$}:{seconds:0width$}",
width = 2
)))
.block(borderless_block())
.bold();
let storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold());
let folders = Paragraph::new(Text::from("Root Folders:")).block(borderless_block().bold());
f.render_widget(version_paragraph, stat_item_areas[0]);
f.render_widget(uptime_paragraph, stat_item_areas[1]);
f.render_widget(storage, stat_item_areas[2]);
for i in 0..monitored_disk_space_vec.len() {
let DiskSpace {
path,
free_space,
total_space,
} = &monitored_disk_space_vec[i];
let title = if let Some(path) = path {
path
} else {
&format!("Disk {}", i + 1)
};
let ratio = if *total_space == 0 {
0f64
} else {
1f64 - (*free_space as f64 / *total_space as f64)
};
let space_gauge = line_gauge_with_label(title.as_str(), ratio);
f.render_widget(space_gauge, stat_item_areas[i + 3]);
}
f.render_widget(folders, stat_item_areas[monitored_disk_space_vec.len() + 3]);
for i in 0..monitored_root_folders.len() {
let RootFolder {
path, free_space, ..
} = &monitored_root_folders[i];
let space: f64 = convert_to_gb(*free_space);
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
.block(borderless_block())
.default_color();
f.render_widget(
root_folder_space,
stat_item_areas[i + monitored_disk_space_vec.len() + 4],
)
}
} else {
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
}
}
fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
let block = title_block("Downloads");
let downloads_vec = &app.data.readarr_data.downloads.items;
if !downloads_vec.is_empty() {
f.render_widget(block, area);
let max_items = ((((area.height as f64 / 2.0).floor() * 2.0) as i64) / 2) - 1;
let items = cmp::min(downloads_vec.len(), max_items.unsigned_abs() as usize);
let download_item_areas =
Layout::vertical(iter::repeat_n(Constraint::Length(2), items).collect::<Vec<Constraint>>())
.margin(1)
.split(area);
for i in 0..items {
let DownloadRecord {
title,
sizeleft,
size,
..
} = &downloads_vec[i];
let percent = if *size == 0.0 {
0.0
} else {
1f64 - (*sizeleft / *size)
};
let download_gauge = line_gauge_with_title(title, percent);
f.render_widget(download_gauge, download_item_areas[i]);
}
} else {
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
}
}
fn draw_readarr_logo(f: &mut Frame<'_>, area: Rect) {
let logo_text = Text::from(READARR_LOGO);
let logo = Paragraph::new(logo_text)
.light_magenta()
.block(layout_block().default_color())
.centered();
f.render_widget(logo, area);
}
+46
View File
@@ -0,0 +1,46 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::models::Route;
use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock;
use crate::ui::DrawUi;
use crate::ui::readarr_ui::ReadarrUi;
#[test]
fn test_readarr_ui_accepts() {
for readarr_block in ActiveReadarrBlock::iter() {
assert!(ReadarrUi::accepts(Route::Readarr(readarr_block, None)));
}
}
mod snapshot_tests {
use super::*;
use crate::app::App;
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
use rstest::rstest;
#[rstest]
#[case(ActiveReadarrBlock::Authors, 0)]
#[case(ActiveReadarrBlock::Downloads, 1)]
#[case(ActiveReadarrBlock::Blocklist, 2)]
#[case(ActiveReadarrBlock::History, 3)]
#[case(ActiveReadarrBlock::RootFolders, 4)]
#[case(ActiveReadarrBlock::Indexers, 5)]
#[case(ActiveReadarrBlock::System, 6)]
fn test_readarr_ui_renders_readarr_tabs(
#[case] active_readarr_block: ActiveReadarrBlock,
#[case] index: usize,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_readarr_block.into());
app.data.readarr_data.main_tabs.set_index(index);
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
ReadarrUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("readarr_tabs_{active_readarr_block}"), output);
}
}
}
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Name ▼ Type Status Quality Profile Metadata Profile Books Size Monitored Tags │
│=> Test Author Continuing Standard Standard 0 0.00 GB 🏷 alex │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,54 @@
---
source: src/ui/readarr_ui/readarr_ui_tests.rs
expression: output
---
╭ Authors ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Lidarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Error | <esc> to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│Some error │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Lidarr Version: 1.2.3.4 ││Test download title ││ ⠀⠀⠀⣠⣴⣶⡿⠻⣿⣶⣦⣄⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Radarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Error | <esc> to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│Some error │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Radarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⣠⣶⢶⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Sonarr Version: 1.2.3.4 ╭ Keybindings ──────────────────────────────────────────────────────────────────────────╮ ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Error | <esc> to close ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│Some error │
@@ -3,7 +3,7 @@ source: src/ui/ui_tests.rs
expression: output
---
╭ Managarr - A Servarr management TUI ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Radarr │ Sonarr │ Lidarr <?> to open help│
│ Radarr │ Sonarr │ Lidarr │ Readarr <?> to open help│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭ Stats ──────────────────────────────────────────────────────────────╮╭ Downloads ─────────────────────────────────────────────────────────╮╭──────────────────╮
│Sonarr Version: 1.2.3.4 ││Test Download Title ││ ⠀⠀⠀⣠⣴⣶⣿⣿⣿⣿⣶⣦⣄⠀⠀⠀ │