95 lines
5.4 KiB
TypeScript
95 lines
5.4 KiB
TypeScript
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");
|
||
});
|