| |
| |
| |
| |
| |
| |
| import { describe, it, expect, beforeEach, afterEach } from "vitest"; |
| import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs"; |
| import { mkdtempSync, rmSync } from "fs"; |
| import { tmpdir } from "os"; |
| import { join } from "path"; |
| import * as Y from "yjs"; |
| import { setDataDir, getDataDir, docPath } from "../src/utils.js"; |
| import { debouncedSave, resetSaveTimers } from "../src/create-app.js"; |
|
|
| let tmpDir: string; |
|
|
| beforeEach(() => { |
| tmpDir = mkdtempSync(join(tmpdir(), "collab-test-")); |
| mkdirSync(tmpDir, { recursive: true }); |
| setDataDir(tmpDir); |
| }); |
|
|
| afterEach(() => { |
| resetSaveTimers(); |
| setDataDir(undefined); |
| try { |
| rmSync(tmpDir, { recursive: true, force: true }); |
| } catch {} |
| }); |
|
|
| function makeDoc(text: string): Y.Doc { |
| const ydoc = new Y.Doc(); |
| const fragment = ydoc.getXmlFragment("default"); |
| const el = new Y.XmlElement("paragraph"); |
| el.insert(0, [new Y.XmlText(text)]); |
| fragment.insert(0, [el]); |
| return ydoc; |
| } |
|
|
| describe("2.1 Local persistence", () => { |
| it("2.1.1 debouncedSave writes .yjs file after debounce", async () => { |
| const ydoc = makeDoc("Hello world"); |
|
|
| debouncedSave("test-doc", ydoc); |
|
|
| |
| const p = docPath("test-doc"); |
| expect(existsSync(p)).toBe(false); |
|
|
| |
| await new Promise((r) => setTimeout(r, 2500)); |
|
|
| expect(existsSync(p)).toBe(true); |
| const buf = readFileSync(p); |
| expect(buf.length).toBeGreaterThan(0); |
|
|
| |
| const restored = new Y.Doc(); |
| Y.applyUpdate(restored, new Uint8Array(buf)); |
| const fragment = restored.getXmlFragment("default"); |
| expect(fragment.length).toBeGreaterThan(0); |
| }); |
|
|
| it("2.1.2 docPath reads from configured DATA_DIR", () => { |
| |
| const ydoc = makeDoc("Existing content"); |
| const state = Y.encodeStateAsUpdate(ydoc); |
| const p = docPath("existing"); |
| writeFileSync(p, Buffer.from(state)); |
|
|
| |
| expect(existsSync(p)).toBe(true); |
| const buf = readFileSync(p); |
| const restored = new Y.Doc(); |
| Y.applyUpdate(restored, new Uint8Array(buf)); |
| const fragment = restored.getXmlFragment("default"); |
| expect(fragment.length).toBeGreaterThan(0); |
| }); |
|
|
| it("2.1.3 debounce collapses rapid saves into one write", async () => { |
| const ydoc = makeDoc("Version 1"); |
|
|
| |
| debouncedSave("rapid", ydoc); |
|
|
| const ydoc2 = makeDoc("Version 2"); |
| debouncedSave("rapid", ydoc2); |
|
|
| const ydoc3 = makeDoc("Version 3"); |
| debouncedSave("rapid", ydoc3); |
|
|
| const p = docPath("rapid"); |
|
|
| |
| expect(existsSync(p)).toBe(false); |
|
|
| |
| await new Promise((r) => setTimeout(r, 2500)); |
|
|
| expect(existsSync(p)).toBe(true); |
|
|
| |
| const buf = readFileSync(p); |
| const restored = new Y.Doc(); |
| Y.applyUpdate(restored, new Uint8Array(buf)); |
| const fragment = restored.getXmlFragment("default"); |
| expect(fragment.length).toBeGreaterThan(0); |
| }); |
| }); |
|
|