From 07a00c3ea35b4df7eb23275704dd26f842db76be Mon Sep 17 00:00:00 2001 From: Fuwn Date: Tue, 3 Mar 2026 22:54:56 -0800 Subject: fix(anime): align due cover rendering with due classification --- src/lib/List/Anime/rendering.test.ts | 96 +++++++++++++++++++++++++++ src/lib/List/Anime/rendering.ts | 19 ++++++ src/lib/List/CleanGrid.svelte | 5 +- src/lib/Media/Anime/Airing/Subtitled/match.ts | 13 ++++ src/lib/Media/Anime/Airing/classify.test.ts | 94 +++++++++++++++++++++++++- 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 src/lib/List/Anime/rendering.test.ts create mode 100644 src/lib/List/Anime/rendering.ts (limited to 'src/lib') diff --git a/src/lib/List/Anime/rendering.test.ts b/src/lib/List/Anime/rendering.test.ts new file mode 100644 index 00000000..038e3729 --- /dev/null +++ b/src/lib/List/Anime/rendering.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from "vitest"; +import type { Media } from "$lib/Data/AniList/media"; +import { shouldRenderAnimeCover } from "$lib/List/Anime/rendering"; + +const baseMedia = (id: number): Media => + ({ + id, + idMal: id, + status: "RELEASING", + type: "ANIME", + episodes: 12, + chapters: 0, + volumes: 0, + duration: 24, + format: "TV", + title: { + romaji: `Fixture Show ${id}`, + english: `Fixture Show ${id}`, + native: `Fixture Show ${id}`, + }, + nextAiringEpisode: { + episode: 9, + nativeEpisode: 10, + airingAt: Math.floor(Date.now() / 1000) + 24 * 60 * 60, + nativeAiringAt: Math.floor(Date.now() / 1000) + 36 * 60 * 60, + }, + synonyms: [], + mediaListEntry: { + progress: 8, + progressVolumes: 0, + status: "CURRENT", + score: 0, + repeat: 0, + startedAt: { + year: 2026, + month: 1, + day: 1, + }, + completedAt: { + year: 0, + month: 0, + day: 0, + }, + createdAt: 0, + updatedAt: 0, + customLists: {}, + }, + startDate: { + year: 2026, + month: 1, + }, + endDate: { + year: 2026, + month: 12, + }, + coverImage: { + extraLarge: "https://example.com/cover-xl.jpg", + medium: "https://example.com/cover-md.jpg", + }, + tags: [], + genres: [], + season: "WINTER", + isAdult: false, + relations: { + edges: [], + }, + }) as Media; + +describe("anime cover rendering", () => { + it("renders due media when nativeEpisode indicates user is behind", () => { + const media = baseMedia(194028); + + expect( + shouldRenderAnimeCover(media, { + upcoming: false, + notYetReleased: false, + }), + ).toBe(true); + }); + + it("hides caught-up releasing media outside upcoming lists", () => { + const media = baseMedia(194028); + + media.mediaListEntry = { + ...(media.mediaListEntry as NonNullable), + progress: 9, + }; + + expect( + shouldRenderAnimeCover(media, { + upcoming: false, + notYetReleased: false, + }), + ).toBe(false); + }); +}); diff --git a/src/lib/List/Anime/rendering.ts b/src/lib/List/Anime/rendering.ts new file mode 100644 index 00000000..8a79d74c --- /dev/null +++ b/src/lib/List/Anime/rendering.ts @@ -0,0 +1,19 @@ +import type { Media } from "$lib/Data/AniList/media"; +import { hasDueEpisodes } from "$lib/Media/Anime/Airing/classify"; + +interface AnimeCoverRenderOptions { + upcoming: boolean; + notYetReleased: boolean; +} + +export const shouldRenderAnimeCover = ( + media: Media, + options: AnimeCoverRenderOptions, +): boolean => { + const progress = media.mediaListEntry?.progress || 0; + + if (options.upcoming || options.notYetReleased) return true; + if (media.status === "FINISHED") return progress !== media.episodes; + + return hasDueEpisodes(media); +}; diff --git a/src/lib/List/CleanGrid.svelte b/src/lib/List/CleanGrid.svelte index 006a5e4e..384d4940 100644 --- a/src/lib/List/CleanGrid.svelte +++ b/src/lib/List/CleanGrid.svelte @@ -1,6 +1,7 @@