Files
i-want-to-heal-mmo/src/frontend/saveSync.test.ts
T
2026-07-17 12:47:42 -04:00

61 lines
2.0 KiB
TypeScript

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<typeof setTimeout>;
},
() => 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();
});
});