import { describe, expect, it } from "vitest"; import { PendingSaveSyncRepository, SAVE_SYNC_RETRY_DELAYS_MS, SaveSyncRetryCoordinator } from "./saveSync"; function memoryStorage(initial = "[]") { const values = new Map([["i-want-to-heal:saves:pending-sync:v1", initial]]); return { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => { values.set(key, value); }, }; } describe("pending save sync", () => { it("persists unique dirty slots and tolerates corrupt state", () => { const storage = memoryStorage(); const pending = new PendingSaveSyncRepository(storage); pending.mark(2); pending.mark(2); pending.mark(1); expect(pending.list()).toEqual([2, 1]); pending.clear(2); expect(pending.list()).toEqual([1]); expect(new PendingSaveSyncRepository(memoryStorage("not-json")).list()).toEqual([]); }); it("waits offline and caps automatic retries", async () => { const pending = new PendingSaveSyncRepository(memoryStorage()); pending.mark(1); let online = false; const uploads: number[] = []; const tasks: Array<{ run: () => void; delay: number }> = []; const coordinator = new SaveSyncRetryCoordinator( pending, async (slotId) => { uploads.push(slotId); return false; }, () => online, (run, delay) => { tasks.push({ run, delay }); return tasks.length as unknown as ReturnType; }, () => undefined, ); coordinator.flush(); expect(uploads).toEqual([]); online = true; coordinator.flush(true); await Promise.resolve(); expect(uploads).toEqual([1]); for (const delay of SAVE_SYNC_RETRY_DELAYS_MS) { coordinator.failed(1); expect(tasks[0]?.delay).toBe(delay); tasks.shift()!.run(); await Promise.resolve(); } coordinator.failed(1); expect(tasks).toEqual([]); expect(uploads).toHaveLength(1 + SAVE_SYNC_RETRY_DELAYS_MS.length); coordinator.dispose(); }); });