aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Media/Anime/Airing/classify.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Media/Anime/Airing/classify.test.ts')
-rw-r--r--src/lib/Media/Anime/Airing/classify.test.ts153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/lib/Media/Anime/Airing/classify.test.ts b/src/lib/Media/Anime/Airing/classify.test.ts
new file mode 100644
index 00000000..cad08b43
--- /dev/null
+++ b/src/lib/Media/Anime/Airing/classify.test.ts
@@ -0,0 +1,153 @@
+import { describe, expect, it } from 'vitest';
+import settings from '$stores/settings';
+import type { Media } from '$lib/Data/AniList/media';
+import { season } from '$lib/Media/Anime/season';
+import { hasDueEpisodes, getAnimeEpisodeState } from '$lib/Media/Anime/Airing/classify';
+import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match';
+import type { SubsPlease } from '$lib/Media/Anime/Airing/Subtitled/subsPlease';
+
+const toScheduleTime = (epochSeconds: number) => {
+ const date = new Date(epochSeconds * 1000);
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+
+ return `${hours}:${minutes}`;
+};
+
+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: 8,
+ airingAt: Math.floor(Date.now() / 1000) + 24 * 60 * 60
+ },
+ synonyms: [],
+ mediaListEntry: {
+ progress: 6,
+ progressVolumes: 0,
+ status: 'CURRENT',
+ score: 0,
+ repeat: 0,
+ startedAt: {
+ year: 2025,
+ month: 1,
+ day: 1
+ },
+ completedAt: {
+ year: 0,
+ month: 0,
+ day: 0
+ },
+ createdAt: 0,
+ updatedAt: 0,
+ customLists: {}
+ },
+ startDate: {
+ year: 2025,
+ month: 1
+ },
+ endDate: {
+ year: 2025,
+ month: 12
+ },
+ coverImage: {
+ extraLarge: 'https://example.com/cover-xl.jpg',
+ medium: 'https://example.com/cover-md.jpg'
+ },
+ tags: [],
+ genres: [],
+ season: season(),
+ isAdult: false,
+ relations: {
+ edges: []
+ }
+ }) as Media;
+
+const regressionIds = [192507, 189259, 198767];
+
+describe('anime episode classification', () => {
+ it('prefers nativeEpisode for due/upcoming classification', () => {
+ const media = baseMedia(192507);
+
+ media.nextAiringEpisode = {
+ episode: 7,
+ nativeEpisode: 8,
+ airingAt: Math.floor(Date.now() / 1000) + 6 * 60 * 60,
+ nativeAiringAt: Math.floor(Date.now() / 1000) + 18 * 60 * 60
+ };
+
+ const state = getAnimeEpisodeState(media);
+
+ expect(state.airedEpisodes).toBe(7);
+ expect(hasDueEpisodes(media)).toBe(true);
+ });
+
+ it('treats stale native release data as aired for due detection', () => {
+ const media = baseMedia(189259);
+
+ media.nextAiringEpisode = {
+ episode: 9,
+ airingAt: Math.floor(Date.now() / 1000) + 72 * 60 * 60,
+ nativeAiringAt: Math.floor(Date.now() / 1000) - 60 * 60
+ };
+
+ const state = getAnimeEpisodeState(media);
+
+ expect(state.airedEpisodes).toBe(9);
+ expect(hasDueEpisodes(media)).toBe(true);
+ });
+});
+
+describe('native countdown toggle parity', () => {
+ for (const id of regressionIds) {
+ it(`keeps media ${id} due with native countdown on/off`, () => {
+ const media = baseMedia(id);
+ const subtitledAiringAt = Math.floor(Date.now() / 1000) + 24 * 60 * 60;
+ const nativeAiringAt = subtitledAiringAt + 12 * 60 * 60;
+ const nativeAiringDate = new Date(nativeAiringAt * 1000);
+ const airingDay = nativeAiringDate.toLocaleString('en-US', { weekday: 'long' });
+ const subsPlease = {
+ tz: 'America/Los_Angeles',
+ schedule: {
+ [airingDay]: [
+ {
+ title: media.title.romaji,
+ page: '',
+ image_url: '',
+ time: toScheduleTime(subtitledAiringAt)
+ }
+ ]
+ }
+ } as unknown as SubsPlease;
+
+ media.nextAiringEpisode = {
+ episode: 8,
+ airingAt: nativeAiringAt
+ };
+
+ settings.setKey('displayNativeCountdown', true);
+
+ const nativeOnly = injectAiringTime(media, subsPlease);
+
+ settings.setKey('displayNativeCountdown', false);
+
+ const subtitled = injectAiringTime(media, subsPlease);
+
+ expect(hasDueEpisodes(nativeOnly)).toBe(true);
+ expect(hasDueEpisodes(subtitled)).toBe(true);
+ });
+ }
+});