mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-11-13 12:13:42 +01:00
Boundary snapshots flushed the debounced writes but didn't wait for them, so the snapshot could read the row before the edit it was meant to keep. They now await flushAllModelWrites first. A closing window can't await, so it sends the writes and then the snapshot straight behind them: in the browser the worker keeps that order, and on the desktop the snapshot can still win, leaving the edit saved but unversioned until its next send. The idle tracker kept one timer for all requests, so a write to any request, from another window or sync, restarted the active one's countdown. Each request now has its own, cleared when it fires or when that request is captured at a boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
import { beforeEach, describe, expect, test, vi } from "vite-plus/test";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
calls: [] as string[],
|
|
finishWrites: null as (() => void) | null,
|
|
flushAllModelWrites: vi.fn(),
|
|
rpc: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@yaakapp-internal/models", () => ({
|
|
flushAllModelWrites: mocks.flushAllModelWrites,
|
|
flushAllPendingPatches: vi.fn(),
|
|
}));
|
|
vi.mock("@yaakapp-internal/platform", () => ({ platform: {} }));
|
|
vi.mock("../hooks/useActiveRequestId", () => ({ activeRequestIdAtom: null }));
|
|
vi.mock("./jotai", () => ({ jotaiStore: {} }));
|
|
vi.mock("./rpc", () => ({ rpc: mocks.rpc }));
|
|
|
|
describe("snapshotRequestVersion", () => {
|
|
beforeEach(() => {
|
|
mocks.calls = [];
|
|
mocks.flushAllModelWrites.mockImplementation(
|
|
() =>
|
|
new Promise<void>((resolve) => {
|
|
mocks.calls.push("flush");
|
|
mocks.finishWrites = () => {
|
|
mocks.calls.push("writes landed");
|
|
resolve();
|
|
};
|
|
}),
|
|
);
|
|
mocks.rpc.mockImplementation(async (cmd: string) => {
|
|
mocks.calls.push(cmd);
|
|
return { id: "mv_1" };
|
|
});
|
|
});
|
|
|
|
test("asks for the snapshot only once pending writes have landed", async () => {
|
|
const { snapshotRequestVersion } = await import("./requestVersions");
|
|
snapshotRequestVersion("rq_1", "switch");
|
|
await Promise.resolve();
|
|
expect(mocks.calls).toEqual(["flush"]);
|
|
|
|
mocks.finishWrites?.();
|
|
await vi.waitFor(() =>
|
|
expect(mocks.calls).toEqual(["flush", "writes landed", "models_snapshot_request"]),
|
|
);
|
|
expect(mocks.rpc).toHaveBeenCalledWith("models_snapshot_request", {
|
|
requestId: "rq_1",
|
|
reason: "switch",
|
|
});
|
|
});
|
|
|
|
test("a failed write still lets the snapshot go ahead", async () => {
|
|
mocks.flushAllModelWrites.mockRejectedValue(new Error("write failed"));
|
|
const { snapshotRequestVersion } = await import("./requestVersions");
|
|
snapshotRequestVersion("rq_1", "idle");
|
|
await vi.waitFor(() => expect(mocks.rpc).toHaveBeenCalledTimes(1));
|
|
});
|
|
});
|