import { describe, expect, it } from "vitest";
import {
compactRelativeTime,
formatCount,
formatDate,
formatDateTime,
formatDuration,
relativeTime,
uniquePrefixLength
} from "#lib/format.ts";
describe("formatCount", () => {
it("leaves counts under a thousand alone", () => {
expect(formatCount(0)).toBe("0");
expect(formatCount(1)).toBe("1");
expect(formatCount(999)).toBe("999");
});
it("abbreviates thousands, dropping a trailing zero decimal", () => {
expect(formatCount(1000)).toBe("1k");
expect(formatCount(1200)).toBe("1.2k");
expect(formatCount(12_345)).toBe("12.3k");
expect(formatCount(999_000)).toBe("999k");
});
it("truncates instead of rounding, so a count never overflows its unit", () => {
expect(formatCount(1299)).toBe("1.2k");
expect(formatCount(999_999)).toBe("999.9k");
expect(formatCount(1_999_999)).toBe("1.9m");
});
it("abbreviates millions", () => {
expect(formatCount(1_000_000)).toBe("1m");
expect(formatCount(2_500_000)).toBe("2.5m");
expect(formatCount(1_200_000_000)).toBe("1200m");
});
it("renders nothing for a missing count", () => {
expect(formatCount(undefined)).toBe("");
expect(formatCount(null)).toBe("");
});
});
describe("formatDuration", () => {
it("reads 0s under a second, so an instant run differs from one that never ran", () => {
expect(formatDuration(0)).toBe("0s");
expect(formatDuration(999)).toBe("0s");
});
it("joins only the non-zero chunks", () => {
expect(formatDuration(45_000)).toBe("45s");
expect(formatDuration(90_000)).toBe("1m 30s");
expect(formatDuration(3_600_000)).toBe("1h");
expect(formatDuration(90_061_000)).toBe("1d 1h 1m 1s");
});
it("rejects negative and non-finite input", () => {
expect(formatDuration(-1)).toBe("");
expect(formatDuration(Number.NaN)).toBe("");
expect(formatDuration(Number.POSITIVE_INFINITY)).toBe("");
});
});
describe("relativeTime", () => {
const now = new Date("2026-08-18T12:00:00Z");
it("describes the past, and the future as now", () => {
expect(relativeTime("2026-08-18T11:59:30Z", now)).toBe("30 seconds ago");
expect(relativeTime("2026-08-18T09:00:00Z", now)).toBe("3 hours ago");
expect(relativeTime("2026-08-15T12:00:00Z", now)).toBe("3 days ago");
expect(relativeTime("2026-08-18T14:00:00Z", now)).toBe("now");
});
it("falls back to years past the largest division", () => {
expect(relativeTime("2020-08-18T12:00:00Z", now)).toBe("6 years ago");
});
it("accepts a Date and renders nothing for an unparseable string", () => {
expect(relativeTime(new Date("2026-08-17T12:00:00Z"), now)).toBe("yesterday");
expect(relativeTime("not a date", now)).toBe("");
});
});
describe("compactRelativeTime", () => {
const now = new Date("2026-08-18T12:00:00Z");
it("truncates to the largest whole unit that fits", () => {
expect(compactRelativeTime("2026-08-18T11:59:59Z", now)).toBe("1s ago");
expect(compactRelativeTime("2026-08-18T11:30:00Z", now)).toBe("30m ago");
expect(compactRelativeTime("2026-08-18T02:00:00Z", now)).toBe("10h ago");
expect(compactRelativeTime("2026-08-15T12:00:00Z", now)).toBe("3d ago");
expect(compactRelativeTime("2026-08-01T12:00:00Z", now)).toBe("2w ago");
expect(compactRelativeTime("2026-05-18T12:00:00Z", now)).toBe("3mo ago");
expect(compactRelativeTime("2020-08-18T12:00:00Z", now)).toBe("6y ago");
});
it("reads future timestamps as now", () => {
expect(compactRelativeTime("2026-08-18T12:00:00Z", now)).toBe("now");
expect(compactRelativeTime("2026-08-18T12:00:30Z", now)).toBe("now");
expect(compactRelativeTime("2026-08-20T12:00:00Z", now)).toBe("now");
});
it("renders nothing for an unparseable string", () => {
expect(compactRelativeTime("not a date", now)).toBe("");
});
});
describe("formatDate", () => {
it("renders a short month-day-year", () => {
expect(formatDate(new Date(2026, 7, 18, 12))).toBe("Aug 18, 2026");
});
it("renders nothing for an unparseable string", () => {
expect(formatDate("not a date")).toBe("");
});
});
describe("formatDateTime", () => {
it("appends a time and zone to the date", () => {
// zone abbreviation depends on the runner's timezone, so match the shape
expect(formatDateTime(new Date(2026, 7, 18, 15, 4))).toMatch(/^Aug 18, 2026, 3:04 PM .+$/);
});
it("renders nothing for an unparseable string", () => {
expect(formatDateTime("not a date")).toBe("");
});
});
describe("uniquePrefixLength", () => {
const ids = ["abc1", "abd2", "x9", "abc1"];
it("stops one character past the longest prefix shared with another id", () => {
expect(uniquePrefixLength("abc1", ids)).toBe(3);
expect(uniquePrefixLength("abd2", ids)).toBe(3);
expect(uniquePrefixLength("x9", ids)).toBe(1);
});
it("never runs past the id itself", () => {
expect(uniquePrefixLength("ab", ["ab", "abc"])).toBe(2);
});
});