Server-side save states via js-dos fsChanges hooks

This commit is contained in:
2026-08-31 17:15:24 +02:00
parent 8324ecd1e7
commit 54bc21123a
8 changed files with 420 additions and 10 deletions
+48
View File
@@ -7,6 +7,9 @@ vi.mock("../../lib/api.js", () => ({
getGame: vi.fn(),
bundleUrl: vi.fn(),
setupBundleUrl: vi.fn(),
pullSaves: vi.fn(),
pushSaves: vi.fn(),
deleteSaves: vi.fn(),
}));
// Mock router
@@ -74,4 +77,49 @@ describe("Play", () => {
expect(screen.getByText("Starting emulator...")).toBeTruthy();
});
});
it("starts js-dos with server-backed save hooks", async () => {
api.getGame.mockResolvedValue(makeGame());
api.bundleUrl.mockReturnValue("/games/game-1.jsdos");
const dosMock = vi.fn().mockReturnValue({
save: vi.fn().mockResolvedValue(true),
stop: vi.fn().mockResolvedValue(undefined),
});
window.Dos = dosMock;
try {
render(Play, { props: { id: "game-1" } });
await vi.waitFor(() => {
expect(dosMock).toHaveBeenCalled();
});
const opts = dosMock.mock.calls[0][1];
expect(opts.url).toBe("/games/game-1.jsdos");
expect(opts.autoSave).toBe(true);
// Both normal and setup bundles map to the same per-game save key
await expect(opts.fsChanges.urlToKey("/games/game-1.jsdos")).resolves.toBe("game-1");
// pull() forwards to the API and returns null when there is no save
api.pullSaves.mockResolvedValue(null);
await expect(opts.fsChanges.pull("game-1")).resolves.toBeNull();
expect(api.pullSaves).toHaveBeenCalledWith("game-1");
// push()/delete() forward to the API
api.pushSaves.mockResolvedValue({ status: "saved" });
await opts.fsChanges.push("game-1", new Uint8Array([1]));
expect(api.pushSaves).toHaveBeenCalledWith("game-1", expect.any(Uint8Array));
api.deleteSaves.mockResolvedValue({ status: "deleted" });
await opts.fsChanges.delete("game-1");
expect(api.deleteSaves).toHaveBeenCalledWith("game-1");
// pull() survives API errors so the game still boots
api.pullSaves.mockRejectedValue(new Error("offline"));
await expect(opts.fsChanges.pull("game-1")).resolves.toBeNull();
} finally {
delete window.Dos;
}
});
});