import { describe, expect, it } from "vitest"; import { CHANGELOG_LAST_MODIFIED, RECENT_CHANGELOG_DAYS, formatChangelogAge, hasRecentChangelog, mergeChangelogReleases, } from "./changelog"; import type { Locale } from "@/i18n/config"; import type { ChangelogReleaseSummary } from "./changelog-types"; it("keeps historical anchors chronological and unique across successive older pages", () => { const release = (version: string, publishedAt: string): ChangelogReleaseSummary => ({ version, publishedAt, title: version, summary: "", layout: "compact", features: [], }); const latest = release("0.11.0", "2026-10-03T12:00:00Z"); const anchor = release("0.9.5", "2026-07-01T12:00:00Z"); const middle = release("0.10.0", "2026-09-01T12:00:00Z"); const older = release("0.9.0", "2026-06-01T12:00:00Z"); const initial = [latest]; const anchored = mergeChangelogReleases(initial, [anchor]); const paginated = mergeChangelogReleases(anchored, [middle]); expect(paginated).toEqual([latest, middle, anchor]); expect(mergeChangelogReleases(paginated, [anchor, older])).toEqual([latest, middle, anchor, older]); expect(initial).toEqual([latest]); expect(anchored).toEqual([latest, anchor]); }); /** * The blue dot in the account menu. The terminals are tested relative to * the last input: otherwise the test would turn off by itself as it ages. */ describe("changelog freshness", () => { const DAY = 24 * 60 * 60 * 1000; const latest = Date.parse(`${CHANGELOG_LAST_MODIFIED}T00:00:00Z`); it("lights up on release day, including before UTC midnight", () => { expect(hasRecentChangelog(latest)).toBe(true); // Paris, 1 a.m. on the day of release: again the day before in UTC. expect(hasRecentChangelog(latest - 2 * 60 * 60 * 1000)).toBe(true); }); it("lasts five days, not six", () => { expect(hasRecentChangelog(latest + RECENT_CHANGELOG_DAYS * DAY - 1)).toBe( true, ); expect(hasRecentChangelog(latest + RECENT_CHANGELOG_DAYS * DAY)).toBe( false, ); }); }); /** The age displayed instead of the date, on the public page and in the modal. */ describe("changelog age", () => { const DAY = 24 * 60 * 60 * 1000; const published = Date.parse("2026-07-20T00:00:00Z"); const age = (now: number, locale: Locale = "en") => formatChangelogAge("2026-07-20", locale, now); it("counts in days in the served language", () => { expect(age(published)).toBe("today"); expect(age(published + DAY)).toBe("yesterday"); expect(age(published + 3 * DAY)).toBe("3 days ago"); expect(age(published + 3 * DAY, "fr")).toBe("il y a 3 jours"); expect(age(published + 3 * DAY, "de")).toBe("vor 3 Tagen"); expect(age(published + 3 * DAY, "pt-BR")).toBe("há 3 dias"); expect(age(published + 3 * DAY, "it")).toBe("3 giorni fa"); expect(age(published + 3 * DAY, "es")).toBe("hace 3 días"); }); /** * Dates are written in UTC-midnight: a reader east of UTC opens the * page while today's delivery is still "in the future". Without * floor, the page would say "in 4 hours". */ it("never describes a publication as being in the future", () => { expect(age(published - 4 * 60 * 60 * 1000)).toBe("today"); }); it("switches to months and then years instead of lining up days", () => { expect(age(published + 29 * DAY)).toBe("29 days ago"); expect(age(published + 40 * DAY)).toBe("last month"); expect(age(published + 200 * DAY)).toBe("6 months ago"); expect(age(published + 400 * DAY)).toBe("last year"); }); });