Files
mangabaka-seeder/test/view-model.test.ts
T
2026-10-15 11:31:51 +02:00

95 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, test } from "bun:test";
import type { ReviewCard } from "../src/core/session";
import { GRANULARITY_LABELS, cardFacts, defaultHost, hostChoices, seenBar, seenSpan, stageText, timelineGroups } from "../src/web/view-model";
import { inspected } from "./helpers/covers";
test("hosts: server first when it has room; hidden without storage; disabled when full", () => {
const ids = (c: ReturnType<typeof hostChoices>) => c.map((h) => `${h.id}${h.disabled ? "(disabled)" : ""}`);
expect(ids(hostChoices({ storage: true, storageFull: false }, {}, { catboxNeedsServer: true }))).toEqual(["server", "litterbox", "catbox", "direct"]);
expect(ids(hostChoices({ storage: false, storageFull: false }, {}, { catboxNeedsServer: true }))).toEqual(["litterbox", "catbox", "direct"]);
const full = hostChoices({ storage: true, storageFull: true }, {}, { catboxNeedsServer: true });
expect(ids(full)).toEqual(["server(disabled)", "litterbox", "catbox", "direct"]);
expect(defaultHost(full)).toBe("litterbox");
expect(ids(hostChoices(null, {}, { catboxNeedsServer: true }))).toEqual(["litterbox", "catbox(disabled)", "direct"]);
expect(ids(hostChoices(null, {}, { catboxNeedsServer: false }))).toEqual(["litterbox", "catbox", "direct"]);
});
test("litterbox carries the review-time warning and its chosen lifetime", () => {
const litterbox = hostChoices(null, { litterboxTime: "24h" }, { catboxNeedsServer: false })[0]!;
expect(litterbox.lifetime).toBe("24 hours");
expect(litterbox.warning).toMatch(/review can take longer/);
});
test("stage text with and without progress", () => {
expect(stageText("history")).toBe("Looking through the Wayback Machine…");
expect(stageText("history", 3, 24)).toBe("Looking through the Wayback Machine… 3/24");
});
test("granularity labels", () => {
expect(GRANULARITY_LABELS).toEqual({ 1: "every month", 3: "every 3 months", 6: "every 6 months", 12: "every year" });
});
test("card facts: size, shape, when seen, archive-only", () => {
const card = (cover: ReturnType<typeof inspected>): ReviewCard =>
({ cover, selected: true, type: cover.type, note: "", contentRating: "safe", hostedUrl: null, error: null });
expect(cardFacts(card(inspected({ bytes: new Uint8Array(352813) })))).toEqual(["480×623", "345 KB", "on the page now"]);
expect(cardFacts(card(inspected({
width: 720, height: 1230, shape: "tall", live: false, bytes: new Uint8Array(2048),
candidate: { current: false, seenAt: "20230401000000", lastSeenAt: "20260301000000" },
})))).toEqual(["720×1230", "2 KB", "landing-page art", "seen 2023-04 – 2026-03", "only on archive.org"]);
});
describe("seen ranges and the timeline", () => {
const now = new Date(Date.UTC(2026, 9, 15)); // 2026-10
const card = (candidate: NonNullable<Parameters<typeof inspected>[0]>["candidate"]): ReviewCard => {
const cover = inspected({ candidate });
return { cover, selected: true, type: cover.type, note: "", contentRating: "safe", hostedUrl: null, error: null };
};
const old = card({ current: false, seenAt: "20150312000000", lastSeenAt: "20150901000000" });
const mid = card({ current: false, seenAt: "20190301000000", lastSeenAt: "20220901000000" });
const live = card({ current: true });
const liveArchived = card({ current: true, seenAt: "20230401000000" });
test("the span runs from the earliest sighting to now", () => {
expect(seenSpan([live.cover, old.cover, mid.cover], now)).toEqual({ from: 2015 * 12 + 2, to: 2026 * 12 + 9 });
expect(seenSpan([live.cover], now)).toEqual({ from: 2026 * 12 + 9, to: 2026 * 12 + 9 });
});
test("bars are drawn to scale; current covers reach the right edge", () => {
const span = seenSpan([old.cover, live.cover], now);
const total = span.to - span.from + 1;
expect(seenBar(old.cover, span, now)).toEqual({ left: 0, width: (7 / total) * 100 });
const b = seenBar(liveArchived.cover, span, now);
expect(b.left + b.width).toBeCloseTo(100, 5);
expect(seenBar(live.cover, span, now).width).toBeGreaterThanOrEqual(1.5);
});
test("the timeline groups covers by the year they were last seen, newest first", () => {
const groups = timelineGroups([old, live, mid, liveArchived], now);
expect(groups.map((g) => g.year)).toEqual([2026, 2022, 2015]);
expect(groups[0]!.cards).toEqual([live, liveArchived]);
expect(groups[1]!.cards).toEqual([mid]);
});
});
test("relative time for the history list", async () => {
const { relativeTime } = await import("../src/web/view-model");
const now = new Date("2026-10-15T12:00:00Z");
const ago = (ms: number) => new Date(now.getTime() - ms).toISOString();
expect(relativeTime(ago(20_000), now)).toBe("just now");
expect(relativeTime(ago(5 * 60_000), now)).toBe("5 min ago");
expect(relativeTime(ago(3 * 3_600_000), now)).toBe("3 h ago");
expect(relativeTime(ago(30 * 3_600_000), now)).toBe("yesterday");
expect(relativeTime(ago(4 * 86_400_000), now)).toBe("4 days ago");
expect(relativeTime(ago(40 * 86_400_000), now)).toBe("2026-09-05");
});
test("history marks and summary line", async () => {
const { markText, summaryLine } = await import("../src/web/view-model");
expect(markText({})).toEqual([]);
expect(markText({ downloaded: "x", rehosted: { at: "x", host: "litterbox" }, filed: { at: "x", submissionId: 77 } }))
.toEqual(["downloaded", "rehosted on litterbox", "filed #77"]);
expect(summaryLine({ fresh: 14, present: 3 })).toBe("14 new · 3 on MangaBaka");
expect(summaryLine({ fresh: 1, present: 0 })).toBe("1 new");
});