import { describe, expect, it } from "vitest"; import { SESSION_AUTO_CLOSE_MS, SESSION_WARN_THRESHOLD_MS, formatCountdown, sessionExpiryPhase, timeUntilExpiry, } from "@/lib/session/countdown"; describe("timeUntilExpiry", () => { it("returns sessionExp - now", () => { expect(timeUntilExpiry(1_800_000_000, 1_700_000_000)).toBe(100_000_000); }); it("defaults now to Date.now()", () => { const now = Date.now(); const result = timeUntilExpiry(now + 5_000); expect(result).toBeGreaterThanOrEqual(4_900); expect(result).toBeLessThanOrEqual(5_000); }); it("returns null when there is no known expiry", () => { expect(timeUntilExpiry(null)).toBeNull(); expect(timeUntilExpiry(undefined)).toBeNull(); }); it("returns null for a non-finite expiry", () => { expect(timeUntilExpiry(Number.NaN)).toBeNull(); expect(timeUntilExpiry(Number.POSITIVE_INFINITY)).toBeNull(); }); it("allows a negative remaining time (already expired)", () => { expect(timeUntilExpiry(100, 200)).toBe(-100); }); }); describe("sessionExpiryPhase", () => { const warn = SESSION_WARN_THRESHOLD_MS; // 15 min const close = SESSION_AUTO_CLOSE_MS; // 30s it("returns idle when remaining exceeds the warn window", () => { expect(sessionExpiryPhase(warn + 1)).toBe("idle"); expect(sessionExpiryPhase(null)).toBe("idle"); }); it("returns warn inside the window (close < remaining <= warn)", () => { expect(sessionExpiryPhase(warn)).toBe("warn"); expect(sessionExpiryPhase(close + 1)).toBe("warn"); expect(sessionExpiryPhase(warn / 2)).toBe("warn"); }); it("returns close at or below the close threshold", () => { expect(sessionExpiryPhase(close)).toBe("close"); expect(sessionExpiryPhase(0)).toBe("close"); expect(sessionExpiryPhase(-1)).toBe("close"); }); it("honours custom thresholds", () => { expect(sessionExpiryPhase(10 * 60_000, 15 * 60_000, 5 * 60_000)).toBe("warn"); expect(sessionExpiryPhase(3 * 60_000, 15 * 60_000, 5 * 60_000)).toBe("close"); }); }); describe("formatCountdown", () => { it("formats under an hour as MM:SS", () => { expect(formatCountdown(0)).toBe("00:00"); expect(formatCountdown(59)).toBe("00:59"); expect(formatCountdown(60)).toBe("01:00"); expect(formatCountdown(90)).toBe("01:30"); expect(formatCountdown(599)).toBe("09:59"); }); it("formats hours as H:MM:SS", () => { expect(formatCountdown(3600)).toBe("1:00:00"); expect(formatCountdown(3725)).toBe("1:02:05"); }); it("rounds up to the next whole second", () => { expect(formatCountdown(0.1)).toBe("00:01"); expect(formatCountdown(59.4)).toBe("01:00"); }); it("never goes negative", () => { expect(formatCountdown(-5)).toBe("00:00"); }); it("handles non-finite input", () => { expect(formatCountdown(Number.NaN)).toBe("00:00"); expect(formatCountdown(Number.POSITIVE_INFINITY)).toBe("00:00"); }); });