import { afterAll, afterEach, expect, spyOn, test } from "bun:test"; import { CalendarCache } from "./CalendarCache"; const fetchSpy = spyOn(globalThis, "fetch"); afterEach(() => fetchSpy.mockReset()); afterAll(() => fetchSpy.mockRestore()); const range = { from: "2026-07-01", to: "2026-09-30" }; test("calendar cache shares in-flight reads, retains wider history and invalidates changed versions", async () => { fetchSpy.mockImplementation(Object.assign(async () => Response.json({ from: range.from, to: range.to, days: [{ date: "2026-07-01" }, { date: "2026-09-01" }] }), { preconnect: fetch.preconnect })); const cache = new CalendarCache(); await Promise.all([cache.load("habit", "v1", range), cache.load("habit", "v1", range)]); expect(fetchSpy).toHaveBeenCalledTimes(1); expect(String(fetchSpy.mock.calls[0]![0])).toContain("compact=true"); const subset = await cache.load("habit", "v1", { from: "2026-09-01", to: range.to }); expect(subset.days).toHaveLength(1); expect(fetchSpy).toHaveBeenCalledTimes(1); await cache.load("habit", "v2", range); expect(fetchSpy).toHaveBeenCalledTimes(2); await new CalendarCache().load("habit", "v2", range); expect(fetchSpy).toHaveBeenCalledTimes(3); }); test("a failed history request can be retried without retaining a rejected cache entry", async () => { fetchSpy.mockRejectedValueOnce(new Error("offline")).mockResolvedValue(Response.json({ ...range, days: [] })); const cache = new CalendarCache(); await expect(cache.load("habit", "v1", range)).rejects.toThrow("offline"); await expect(cache.load("habit", "v1", range)).resolves.toMatchObject({ days: [] }); expect(fetchSpy).toHaveBeenCalledTimes(2); });