fix: show N/A instead of zeros when a season, album or book has no statistics

The season, album and book tables rendered 0/0 and 0.00 GB whenever the
API omitted the statistics object for a row, presenting fabricated counts
as real. The row colour already treated that case as indeterminate; the
cell text now does too, rendering N/A for both cells, matching what these
tables already do for a missing release date.

Sonarr's season row is restructured from a destructure-with-default to the
same Option-aware map_or_else shape lidarr and readarr use.

The readarr test fixture book has no statistics, so its existing author
details snapshots change from the fabricated zeros to N/A on exactly that
row.
This commit is contained in:
Dark-Alex-17 committed 2026-09-17 14:42:54 -06:00
1 parent 9b4b3003b5
commit 76c490f45b
19 files changed
+238 -40

No files matched your search

@@ -279,13 +279,16 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.release_date
.map_or_else(|| "N/A".to_owned(), |d| d.format("%Y-%m-%d").to_string());
let track_count = album.statistics.as_ref().map_or_else(
|| "0/0".to_owned(),
|| "N/A".to_owned(),
|s| format!("{}/{}", s.track_file_count, s.total_track_count),
);
let size = album
.statistics
.as_ref()
.map_or(0f64, |s| convert_to_gb(s.size_on_disk));
let size = album.statistics.as_ref().map_or_else(
|| "N/A".to_owned(),
|s| {
let size = convert_to_gb(s.size_on_disk);
format!("{size:.2} GB")
},
);
let duration_mins = album.duration / 60000;
decorate_album_row_with_style(
@@ -297,7 +300,7 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from(track_count),
Cell::from(format!("{duration_mins} min")),
Cell::from(release_date),
Cell::from(format!("{size:.2} GB")),
Cell::from(size),
]),
)
};
@@ -249,6 +249,19 @@ mod tests {
);
}
#[test]
fn test_artist_details_ui_renders_album_without_statistics() {
let mut app = App::test_default_fully_populated();
app.data.lidarr_data.albums.items[0].statistics = None;
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
ArtistDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_artist_details_ui_renders_delete_album_prompt_over_artist_details() {
let mut app = App::test_default_fully_populated();
@@ -0,0 +1,52 @@
---
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
expression: output
---
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│Artist: Alex │
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
│seven years to finish. │
│Disambiguation: American pianist │
│Type: Person │
│Status: Continuing │
│Genres: soundtrack │
│Rating: 84% │
│Path: /nfs/music/test-artist │
│Quality Profile: Lossless │
│Metadata Profile: Standard │
│Monitored: Yes │
│Albums: 1 │
│Tracks: 12/15 │
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
││ Albums │ History │ Manual Search ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Title Type Tracks Duration Release Date Size ││
││=> 🏷 Test Album Album N/A 0 min 2023-01-01 N/A ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -275,13 +275,16 @@ fn draw_books_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.release_date
.map_or_else(|| "N/A".to_owned(), |d| d.format("%Y-%m-%d").to_string());
let book_file_count = book.statistics.as_ref().map_or_else(
|| "0/0".to_owned(),
|| "N/A".to_owned(),
|s| format!("{}/{}", s.book_file_count, s.total_book_count),
);
let size = book
.statistics
.as_ref()
.map_or(0f64, |s| convert_to_gb(s.size_on_disk));
let size = book.statistics.as_ref().map_or_else(
|| "N/A".to_owned(),
|s| {
let size = convert_to_gb(s.size_on_disk);
format!("{size:.2} GB")
},
);
decorate_book_row_with_style(
book,
@@ -290,7 +293,7 @@ fn draw_books_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from(book.title.to_string()),
Cell::from(book_file_count),
Cell::from(release_date),
Cell::from(format!("{size:.2} GB")),
Cell::from(size),
]),
)
};
@@ -271,6 +271,19 @@ mod tests {
);
}
#[test]
fn test_author_details_ui_renders_book_without_statistics() {
let mut app = App::test_default_fully_populated();
app.data.readarr_data.books.items[0].statistics = None;
app.push_navigation_stack(ActiveReadarrBlock::AuthorDetails.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
AuthorDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_author_details_ui_renders_update_and_scan_prompt_over_author_details() {
let mut app = App::test_default_fully_populated();
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Title Files Release Date Size ││
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
││=> 🏷 Test Book N/A 2023-05-20 N/A ││
││ ││
││ ││
││ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
││ Monitored Title │ │Date Size ││
││=> 🏷 Test Book │ │20 0.00 GB ││
││=> 🏷 Test Book │ │20 N/A ││
││ │ │ ││
││ │ │ ││
││ │ │ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Title Files Release Date Size ││
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
││=> 🏷 Test Book N/A 2023-05-20 N/A ││
││ ││
││ ││
││ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Title Files Release Date Size ││
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
││=> 🏷 Test Book N/A 2023-05-20 N/A ││
││ ││
││ ││
││ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search │ Do you want to trigger an update and disk scan for the │ ││
││─────────────────────────────────────────│ author: Test Author? │───────────────────────────────────────────││
││ Monitored Title │ │Date Size ││
││=> 🏷 Test Book │ │20 0.00 GB ││
││=> 🏷 Test Book │ │20 N/A ││
││ │ │ ││
││ │ │ ││
││ │ │ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
││ Monitored Title │ │Date Size ││
││=> 🏷 Test Book │ │20 0.00 GB ││
││=> 🏷 Test Book │ │20 N/A ││
││ │ │ ││
││ │ │ ││
││ │ │ ││
@@ -0,0 +1,52 @@
---
source: src/ui/readarr_ui/library/author_details_ui_tests.rs
expression: output
---
╭ Test Author ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│Author: Test Author │
│Status: Continuing │
│Genres: science fiction │
│Rating: N/A │
│Path: /nfs/books/Test Author │
│Quality Profile: Standard │
│Metadata Profile: Comprehensive │
│Monitored: Yes │
│Overview: some interesting description of the author He was born in Madison, Wisconsin: a city he has never really left. His first novel took him │
│seven years to finish. │
│ │
│ │
│╭ Author Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
││ Books │ History │ Author Search ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Title Files Release Date Size ││
││=> 🏷 Test Book N/A 2023-05-20 N/A ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search │ Do you really want to delete the book: │ ││
││─────────────────────────────────────────│ Test Book? │───────────────────────────────────────────││
││ Monitored Title │ │Date Size ││
││=> 🏷 Test Book │ │20 0.00 GB ││
││=> 🏷 Test Book │ │20 N/A ││
││ │ ╭───╮ │ ││
││ │ Delete Book Files: │ ✔ │ │ ││
││ │ ╰───╯ │ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search │ Do you want to trigger an update and disk scan for the │ ││
││─────────────────────────────────────────│ author: Test Author? │───────────────────────────────────────────││
││ Monitored Title │ │Date Size ││
││=> 🏷 Test Book │ │20 0.00 GB ││
││=> 🏷 Test Book │ │20 N/A ││
││ │ │ ││
││ │ │ ││
││ │ │ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History ││ ╰───╯ │ ││
││──────────────────│ ╭─────────────────────────────────────────────────╮ │────────────────────││
││ Monitored │ Monitor New Books: │All Books ▼ │ │ ││
││=> 🏷 │ ╰─────────────────────────────────────────────────╯ │B ││
││=> 🏷 │ ╰─────────────────────────────────────────────────╯ │ ││
││ │ ╭─────────────────────────────────────────────────╮ │ ││
││ │ Quality Profile: │eBook ▼ │ │ ││
││ │ ╰─────────────────────────────────────────────────╯ │ ││
@@ -22,7 +22,7 @@ expression: output
││ Books │ History │ Author Search ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Title Files Release Date Size ││
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
││=> 🏷 Test Book N/A 2023-05-20 N/A ││
││ ││
││ ││
││ ││
+15 -18
View File
@@ -10,7 +10,7 @@ use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SERIES_DETAILS_BLOCKS};
use crate::models::sonarr_models::{Season, SeasonStatistics, SonarrHistoryItem};
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
use crate::ui::sonarr_ui::library::episode_details_ui::EpisodeDetailsUi;
use crate::ui::sonarr_ui::library::season_details_ui::SeasonDetailsUi;
use crate::ui::sonarr_ui::library::series_overview_ui::SeriesOverviewUi;
@@ -229,29 +229,26 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let content = Some(&mut app.data.sonarr_data.seasons);
let season_row_mapping = |season: &Season| {
let Season {
title, statistics, ..
} = season;
let SeasonStatistics {
episode_file_count,
episode_count,
size_on_disk,
..
} = if let Some(stats) = statistics {
stats
} else {
&SeasonStatistics::default()
};
let season_monitored = if season.monitored { "🏷" } else { "" };
let size = convert_to_gb(*size_on_disk);
let episode_count = season.statistics.as_ref().map_or_else(
|| "N/A".to_owned(),
|s| format!("{}/{}", s.episode_file_count, s.episode_count),
);
let size = season.statistics.as_ref().map_or_else(
|| "N/A".to_owned(),
|s| {
let size = convert_to_gb(s.size_on_disk);
format!("{size:.2} GB")
},
);
decorate_season_row_with_style(
season,
Row::new(vec![
Cell::from(season_monitored.to_owned()),
Cell::from(title.clone().unwrap_or_default()),
Cell::from(format!("{episode_file_count}/{episode_count}")),
Cell::from(format!("{size:.2} GB")),
Cell::from(season.title.clone().unwrap_or_default()),
Cell::from(episode_count),
Cell::from(size),
]),
)
};
@@ -239,6 +239,19 @@ mod tests {
);
}
#[test]
fn test_series_details_ui_renders_season_without_statistics() {
let mut app = App::test_default_fully_populated();
app.data.sonarr_data.seasons.items[0].statistics = None;
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
SeriesDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_series_details_ui_renders_season_details_over_series_details() {
let mut app = App::test_default_fully_populated();
@@ -0,0 +1,52 @@
---
source: src/ui/sonarr_ui/library/series_details_ui_tests.rs
expression: output
---
╭ Test ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│Title: Test │
│Overview: Blah blah blah It was filmed in Madison, Wisconsin: a city the crew never really left. The first season took seven years to finish. │
│Network: HBO │
│Status: Continuing │
│Genres: cool, family, fun │
│Rating: 84% │
│Year: 2022 │
│Runtime: 63 minutes │
│Path: /nfs/tv/Test │
│Quality Profile: Bluray-1080p │
│Language Profile: English │
│Monitored: Yes │
│Size on Disk: 59.51 GB │
│ │
│ │
│ │
│╭ Series Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
││ Seasons │ History ││
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ Monitored Season Episode Count Size on Disk ││
││=> 🏷 Season title N/A N/A ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯