* fix(metadata): persist series show_status from provider metadata
Plugin-reported series status (proto status field 31) was mapped into
MetadataResult.ShowStatus but dropped by both metadataResultToItem and
itemToMetadataResult, so media_items.show_status stayed empty for every
movie and series - only the manga enrichment path ever wrote it. This
left the Show Status card overlay permanently blank for series.
- carry ShowStatus through both converters; series values normalize to
a canonical lowercase domain (returning/ended/cancelled/in_production/
upcoming) so TMDB "Returning Series"/"Canceled" and TVDB
"Continuing"/"Upcoming" converge on one spelling
- pass non-series values through verbatim so the manga status domain
("Ongoing", ...) can never be mangled by a generic refresh round-trip
- round-tripping the existing item's status also stops refreshes from
wiping a previously persisted value via show_status = EXCLUDED.show_status
- extend the web overlay formatter with continuing/upcoming/planned
TMDB/TVDB plugins need follow-up changes to actually emit the status
field; prepped separately in their repos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(web): overlay badge layout, ordering, and wordmark rendering
Fixes from a full card-overlay audit (every issue verified by DOM
geometry measurement before/after):
- render each card edge as one flex row holding both corner stacks so
opposing badges share the width (min-w-0 + truncate) instead of
overlapping on narrow cards; long labels ellipsize instead of
wrapping over the opposite corner
- honor prefs.order via orderedOverlaysForPosition — the renderer
previously ignored the stored order entirely
- cap corners at 3 badges so maxed-out configs can't collide with the
opposite vertical corner
- lift bottom-right badges above the card menu button, which is always
visible on touch devices and occluded them
- suppress the text label when a wordmark icon (HDR10/ATMOS/AV1/HDR)
already spells it — pill/vibrant presets rendered "HDR10 HDR10" —
and widen the wordmark viewBoxes, which clipped their own text;
drop the never-used iconOnly flag the wordmark rule supersedes
- standalone resolution badge now uses prettyResolution ("4K", not
"2160P"), matching the combined badge
- manga cards skip generic overlays (their status/count chips own both
top corners) and the two chips now share a row and truncate instead
of overlapping each other
- useOverlayPrefs returns null while loading so cards no longer flash
default badges before the user's config or admin kill switch arrives
- settings rows for Resolution/HDR now say why they're hidden while
the combined badge is enabled
- add a CardOverlays test suite covering every registered overlay,
ordering, suppression, wordmarks, the corner cap, and menu clearance
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
package metadata
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/models"
|
|
)
|
|
|
|
func TestNormalizeShowStatus(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{"", ""},
|
|
{" ", ""},
|
|
// TMDB spellings
|
|
{"Returning Series", "returning"},
|
|
{"Ended", "ended"},
|
|
{"Canceled", "cancelled"},
|
|
{"In Production", "in_production"},
|
|
{"Pilot", "in_production"},
|
|
{"Planned", "upcoming"},
|
|
// TVDB spellings
|
|
{"Continuing", "returning"},
|
|
{"Upcoming", "upcoming"},
|
|
// Already-canonical values are stable
|
|
{"returning", "returning"},
|
|
{"ended", "ended"},
|
|
{"cancelled", "cancelled"},
|
|
{"in_production", "in_production"},
|
|
{"upcoming", "upcoming"},
|
|
// Unknown values pass through lowercased instead of being dropped
|
|
{"On Hiatus", "on hiatus"},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := NormalizeShowStatus(tc.in); got != tc.want {
|
|
t.Errorf("NormalizeShowStatus(%q) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMetadataResultToItem_NormalizesSeriesShowStatus(t *testing.T) {
|
|
result := &MetadataResult{
|
|
HasMetadata: true,
|
|
Title: "Series",
|
|
ShowStatus: "Returning Series",
|
|
}
|
|
|
|
item := metadataResultToItem(result, "series")
|
|
if item.ShowStatus != "returning" {
|
|
t.Fatalf("expected item show_status %q, got %q", "returning", item.ShowStatus)
|
|
}
|
|
}
|
|
|
|
func TestMetadataResultToItem_PassesNonSeriesShowStatusVerbatim(t *testing.T) {
|
|
// Manga statuses use their own value domain ("Ongoing", "Completed", ...)
|
|
// normalized by the manga enrichment pipeline; the generic converter must
|
|
// not case-mangle them on a round-trip.
|
|
result := &MetadataResult{
|
|
HasMetadata: true,
|
|
Title: "Manga",
|
|
ShowStatus: "Ongoing",
|
|
}
|
|
|
|
item := metadataResultToItem(result, "manga")
|
|
if item.ShowStatus != "Ongoing" {
|
|
t.Fatalf("expected manga show_status to pass through verbatim, got %q", item.ShowStatus)
|
|
}
|
|
}
|
|
|
|
func TestItemToMetadataResult_CarriesShowStatus(t *testing.T) {
|
|
result := itemToMetadataResult(&models.MediaItem{
|
|
ContentID: "series-1",
|
|
Type: "series",
|
|
Title: "Series",
|
|
ShowStatus: "returning",
|
|
})
|
|
|
|
if result.ShowStatus != "returning" {
|
|
t.Fatalf("expected metadata show_status %q, got %q", "returning", result.ShowStatus)
|
|
}
|
|
}
|
|
|
|
// A refresh cycle that fetches no status must not wipe a previously persisted
|
|
// one: the existing item's status round-trips through itemToMetadataResult,
|
|
// survives the merge (fresh empty values never overwrite), and lands back on
|
|
// the item built for the upsert.
|
|
func TestShowStatus_SurvivesRefreshWithoutProviderStatus(t *testing.T) {
|
|
existing := itemToMetadataResult(&models.MediaItem{
|
|
ContentID: "series-1",
|
|
Type: "series",
|
|
Title: "Series",
|
|
ShowStatus: "ended",
|
|
})
|
|
fresh := &MetadataResult{HasMetadata: true, Title: "Series"}
|
|
|
|
MergeMetadata(fresh, existing, nil, MergeReplaceUnlocked)
|
|
item := metadataResultToItem(existing, "series")
|
|
|
|
if item.ShowStatus != "ended" {
|
|
t.Fatalf("expected persisted show_status to survive refresh, got %q", item.ShowStatus)
|
|
}
|
|
}
|