mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-14 20:20:12 +00:00
Share the CLI watcher with a reconciled name inventory, observe local mutations, and retain newer notifications across overlapping reads. Adopt the shared index for Obsidian and preserve fresh bulk operations.
122 lines
4.5 KiB
TypeScript
122 lines
4.5 KiB
TypeScript
import { fs, os, path, UnicodeResolvingNodeStorageAdapter } from "@vrtmrz/livesync-commonlib/node";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { ObservedNodeStorageAdapter } from "./ObservedNodeStorageAdapter";
|
|
|
|
function makeRoot(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), "livesync-observed-storage-"));
|
|
}
|
|
|
|
function removeRoot(root: string): void {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
|
|
describe("ObservedNodeStorageAdapter", () => {
|
|
it("writes using the default Legacy comparison callbacks", async () => {
|
|
const root = makeRoot();
|
|
const events: string[] = [];
|
|
const storage = new ObservedNodeStorageAdapter(root, undefined, undefined, (paths) => {
|
|
events.push(`start:${paths.join(",")}`);
|
|
return () => events.push("complete");
|
|
});
|
|
|
|
try {
|
|
await storage.write("Legacy.md", "legacy contents");
|
|
|
|
expect(fs.readFileSync(path.join(root, "Legacy.md"), "utf8")).toBe("legacy contents");
|
|
expect(events).toEqual(["start:Legacy.md", "complete"]);
|
|
} finally {
|
|
removeRoot(root);
|
|
}
|
|
});
|
|
|
|
it("writes an NFC name with version 1 and the default case comparison callback", async () => {
|
|
const root = makeRoot();
|
|
const nfcPath = "caf\u00e9.md";
|
|
const events: string[] = [];
|
|
const storage = new ObservedNodeStorageAdapter(
|
|
root,
|
|
() => 1,
|
|
undefined,
|
|
(paths) => {
|
|
events.push(`start:${paths.join(",")}`);
|
|
return () => events.push("complete");
|
|
}
|
|
);
|
|
|
|
try {
|
|
await storage.write(nfcPath, "nfc contents");
|
|
|
|
expect(fs.readFileSync(path.join(root, nfcPath), "utf8")).toBe("nfc contents");
|
|
expect(events).toEqual([`start:${nfcPath}`, "complete"]);
|
|
} finally {
|
|
removeRoot(root);
|
|
}
|
|
});
|
|
|
|
it("preserves successful filesystem writes when observation setup throws", async () => {
|
|
const root = makeRoot();
|
|
const failure = new Error("observation setup failed");
|
|
const onMutation = vi.fn(() => {
|
|
throw failure;
|
|
});
|
|
const storage = new ObservedNodeStorageAdapter(root, undefined, undefined, onMutation);
|
|
|
|
try {
|
|
await expect(
|
|
storage.write("observer-start.md", "written despite observer failure")
|
|
).resolves.toBeUndefined();
|
|
|
|
expect(onMutation).toHaveBeenCalledWith(["observer-start.md"]);
|
|
expect(fs.readFileSync(path.join(root, "observer-start.md"), "utf8")).toBe(
|
|
"written despite observer failure"
|
|
);
|
|
} finally {
|
|
removeRoot(root);
|
|
}
|
|
});
|
|
|
|
it("preserves successful filesystem writes when invalidation completion throws", async () => {
|
|
const root = makeRoot();
|
|
const failure = new Error("invalidation completion failed");
|
|
const complete = vi.fn(() => {
|
|
throw failure;
|
|
});
|
|
const onMutation = vi.fn(() => complete);
|
|
const storage = new ObservedNodeStorageAdapter(root, undefined, undefined, onMutation);
|
|
|
|
try {
|
|
await expect(
|
|
storage.write("observer-complete.md", "written despite completion failure")
|
|
).resolves.toBeUndefined();
|
|
|
|
expect(onMutation).toHaveBeenCalledWith(["observer-complete.md"]);
|
|
expect(complete).toHaveBeenCalledOnce();
|
|
expect(fs.readFileSync(path.join(root, "observer-complete.md"), "utf8")).toBe(
|
|
"written despite completion failure"
|
|
);
|
|
} finally {
|
|
removeRoot(root);
|
|
}
|
|
});
|
|
|
|
it("runs invalidation completion and preserves the underlying I/O error", async () => {
|
|
const root = makeRoot();
|
|
const failure = new Error("underlying write failed");
|
|
const complete = vi.fn();
|
|
const onMutation = vi.fn(() => complete);
|
|
const storage = new ObservedNodeStorageAdapter(root, undefined, undefined, onMutation);
|
|
const write = vi.spyOn(UnicodeResolvingNodeStorageAdapter.prototype, "write").mockRejectedValue(failure);
|
|
|
|
try {
|
|
await expect(storage.write("failed-write.md", "contents")).rejects.toBe(failure);
|
|
|
|
expect(onMutation).toHaveBeenCalledWith(["failed-write.md"]);
|
|
expect(complete).toHaveBeenCalledOnce();
|
|
expect(fs.existsSync(path.join(root, "failed-write.md"))).toBe(false);
|
|
} finally {
|
|
write.mockRestore();
|
|
removeRoot(root);
|
|
}
|
|
});
|
|
});
|