Files
arr-tui/internal/ui/calendar_test.go
T

145 lines
5.4 KiB
Go

package ui
import (
"strings"
"testing"
"time"
"git.stinnesbeck.com/nils/arr-tui/internal/radarr"
"git.stinnesbeck.com/nils/arr-tui/internal/sonarr"
)
func day(y int, m time.Month, d int) time.Time { return time.Date(y, m, d, 0, 0, 0, 0, time.Local) }
func TestMovieEntriesPerReleaseDate(t *testing.T) {
from, to := day(2026, 10, 20), day(2026, 11, 2)
now := day(2026, 10, 21).Add(10 * time.Hour)
m := radarr.Movie{
ID: 4, Title: "Dune [Part 2]", Year: 2024, Monitored: true,
InCinemas: "2026-10-22T00:00:00Z", // inside
DigitalRelease: "2026-11-01T00:00:00Z", // inside
// physical release is after the window
PhysicalRelease: "2026-12-01T00:00:00Z",
}
es := movieEntries(m, from, to, coverRef{}, now)
if len(es) != 2 {
t.Fatalf("%d entries, want 2: %+v", len(es), es)
}
if es[0].sub != "In cinemas" || !es[0].day.Equal(day(2026, 10, 22)) || es[1].sub != "Digital release" || !es[1].day.Equal(day(2026, 11, 1)) {
t.Errorf("entries = %+v", es)
}
if !es[0].movie || es[0].missing || !strings.Contains(es[0].label, "Dune [Part 2[]") {
t.Errorf("flags/label wrong: %+v", es[0])
}
if es[0].key() == es[1].key() {
t.Error("the same movie on two dates must have distinct keys")
}
if !strings.Contains(es[0].details.head, "Release: In cinemas, Thu 22 Oct 2026") {
t.Errorf("details: %q", es[0].details.head)
}
// A release that already happened and is not downloaded counts as missing.
past := movieEntries(radarr.Movie{ID: 5, Title: "Old", Monitored: true, DigitalRelease: "2026-10-20T00:00:00Z"}, from, to, coverRef{}, now)
if len(past) != 1 || !past[0].missing {
t.Errorf("past release: %+v", past)
}
// Dates are taken as written: midnight UTC stays on that calendar day in every zone.
if es := movieEntries(radarr.Movie{ID: 6, Title: "Z", InCinemas: "2026-10-25T00:00:00Z"}, from, to, coverRef{}, now); len(es) != 1 || es[0].day.Day() != 25 {
t.Errorf("timezone shifted the release date: %+v", es)
}
if es := movieEntries(radarr.Movie{ID: 7, Title: "NoDates"}, from, to, coverRef{}, now); len(es) != 0 {
t.Errorf("movie without dates: %+v", es)
}
}
func TestEpisodeEntry(t *testing.T) {
from, to := day(2026, 10, 20), day(2026, 11, 2)
now := day(2026, 10, 21).Add(12 * time.Hour)
at := time.Date(2026, 10, 22, 19, 30, 0, 0, time.UTC)
e := sonarr.Episode{
ID: 9, SeasonNumber: 2, EpisodeNumber: 5, Title: "Welcome", Monitored: true,
AirDateUTC: at.Format(time.RFC3339), Overview: "Episode plot",
Series: &sonarr.Series{Title: "Andor", Overview: "Series plot"},
}
got, ok := episodeEntry(e, from, to, coverRef{}, now)
if !ok {
t.Fatal("episode in the window rejected")
}
if got.sub != "S02E05 Welcome" || got.title != "Andor" || got.movie || got.episodeID != 9 || got.at.IsZero() {
t.Errorf("entry = %+v", got)
}
if !got.day.Equal(localDay(at.Local())) {
t.Errorf("day = %v, want the local date of the airing", got.day)
}
if got.details.plot != "Episode plot" || !strings.Contains(got.details.head, "Andor") || !strings.Contains(got.details.head, "S02E05") {
t.Errorf("details = %+v", got.details)
}
if got.missing {
t.Error("an episode that has not aired yet is not missing")
}
// Falls back to the series plot, and to the date-only air date.
e2 := sonarr.Episode{ID: 10, Title: "X", AirDate: "2026-10-19", Monitored: true, Series: &sonarr.Series{Title: "S", Overview: "Series plot"}}
if _, ok := episodeEntry(e2, from, to, coverRef{}, now); ok {
t.Error("an episode before the window must be left out")
}
e2.AirDate = "2026-10-20"
got, ok = episodeEntry(e2, from, to, coverRef{}, now)
if !ok || got.details.plot != "Series plot" || !got.at.IsZero() || !got.missing {
t.Errorf("date-only episode: ok=%v %+v", ok, got)
}
if _, ok := episodeEntry(sonarr.Episode{ID: 11}, from, to, coverRef{}, now); ok {
t.Error("an episode without any date has no place in the calendar")
}
}
func TestSortEntriesAndDayLabel(t *testing.T) {
d1, d2 := day(2026, 10, 21), day(2026, 10, 22)
es := []calEntry{
{day: d2, title: "b"},
{day: d1, at: d1.Add(20 * time.Hour), title: "late"},
{day: d1, title: "dateonly"},
{day: d1, at: d1.Add(9 * time.Hour), title: "early"},
{day: d1, at: d1.Add(9 * time.Hour), title: "Alpha"},
}
sortEntries(es)
var got []string
for _, e := range es {
got = append(got, e.title)
}
if strings.Join(got, ",") != "dateonly,Alpha,early,late,b" {
t.Errorf("order = %v", got)
}
today := day(2026, 10, 21)
type label struct{ date, rel string }
for d, want := range map[time.Time]label{
today: {"Wed 21 Oct", "Today"},
today.AddDate(0, 0, 1): {"Thu 22 Oct", "Tomorrow"},
today.AddDate(0, 0, -1): {"Tue 20 Oct", "Yesterday"},
today.AddDate(0, 0, 5): {"Mon 26 Oct", ""},
} {
if date, rel := dayLabel(d, today); (label{date, rel}) != want {
t.Errorf("dayLabel(%v) = %q, %q, want %v", d, date, rel, want)
}
}
}
func TestMergeEntries(t *testing.T) {
d1, d2, d3 := day(2026, 10, 21), day(2026, 10, 22), day(2026, 10, 23)
have := []calEntry{{day: d2, title: "b", episodeID: 2}, {day: d3, title: "c", episodeID: 3}}
add := []calEntry{
{day: d1, title: "a", episodeID: 1},
{day: d2, title: "b", episodeID: 2}, // already there: not duplicated
{day: d3, title: "film", movie: true, movieID: 7, sub: "In cinemas"},
}
got := mergeEntries(have, add)
var titles []string
for _, e := range got {
titles = append(titles, e.title)
}
if strings.Join(titles, ",") != "a,b,c,film" {
t.Errorf("merged = %v", titles)
}
}