/* Golden tests pinning lib/sync.js — the three gates, as the artifact applies them to four whole documents. The port syncs rows instead and applies the same rules row by row; these pin the reference behaviour. */ import { describe, it, expect } from "vitest"; import { weigh, isLater, reconcile, makeWriter } from "@lib/sync.js"; import type { RemoteDoc } from "@lib/sync.js"; describe("weigh — how much a copy holds", () => { it("counts turns, finished units, cards and days", () => { expect(weigh("chat", { turns: [1, 2, 3] })).toBe(3); expect(weigh("meta", { road: { done: { "1.1": 1 } } })).toBe(1); expect(weigh("srs", { cards: { a: 1, b: 2 } })).toBe(2); expect(weigh("log", { days: {} })).toBe(0); expect(weigh("chat", null)).toBe(0); }); }); describe("isLater — a counter, not a clock", () => { it("compares counters when both sides have one, whatever the clocks say", () => { expect(isLater({ v: 3, u: 1 }, 2, 99)).toBe(true); expect(isLater({ v: 1, u: 999 }, 2, 1)).toBe(false); }); it("breaks a counter tie on the stamp", () => { expect(isLater({ v: 2, u: 5 }, 2, 4)).toBe(true); expect(isLater({ v: 2, u: 4 }, 2, 4)).toBe(false); }); it("falls back to the clock only when one side has no counter", () => { expect(isLater({ u: 5 }, 0, 4)).toBe(true); expect(isLater({ v: 9 }, 0, 4)).toBe(false); }); }); describe("reconcile — no silent shrinking", () => { const local = { version: 3, stamp: 100, data: { turns: [1, 2, 3] } }; it("reasserts ours when a later copy holds less and nobody said so", () => { expect(reconcile("chat", { v: 4, u: 200, d: { turns: [1] } }, local)).toBe("reassert"); }); it("obeys a deliberate shrink", () => { expect(reconcile("chat", { v: 4, u: 200, d: { turns: [1] }, x: 1 }, local)).toBe("adopt"); }); it("adopts a later copy that holds at least as much", () => { expect(reconcile("chat", { v: 4, u: 200, d: { turns: [1, 2, 3, 4] } }, local)).toBe("adopt"); }); it("ignores an older copy, and a missing one", () => { expect(reconcile("chat", { v: 2, u: 900, d: { turns: [] } }, local)).toBe("ignore"); expect(reconcile("chat", null, local)).toBe("ignore"); }); }); describe("makeWriter — hydration", () => { function writer() { let t = 1000; const pushed: [string, RemoteDoc][] = []; const w = makeWriter({ push: (name, body) => { pushed.push([name, body]); return "sent"; }, now: () => ++t, }); return { w, pushed }; } it("holds an edit made before hydration, unstamped and unpushed", () => { const { w, pushed } = writer(); expect(w.touch("chat")).toMatchObject({ version: 0, stamp: 0, dirty: true, hydrated: false }); expect(w.flush("chat", { turns: [] })).toBeNull(); expect(pushed).toEqual([]); }); it("stamps the held edit when hydration finds nothing newer, then pushes it", () => { const { w, pushed } = writer(); w.touch("chat"); expect(w.hydrate("chat")).toMatchObject({ version: 1, stamp: 1001, hydrated: true }); expect(w.flush("chat", { turns: [1] })).toBe("sent"); expect(pushed).toEqual([["chat", { u: 1001, v: 1, d: { turns: [1] } }]]); }); it("carries a deliberate shrink as x on exactly one push", () => { const { w, pushed } = writer(); w.touch("meta", true); w.hydrate("meta"); w.flush("meta", {}); expect(pushed[0]![1]).toMatchObject({ x: 1 }); w.touch("meta"); w.flush("meta", {}); expect(pushed[1]![1]).not.toHaveProperty("x"); }); });