mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-14 20:20:12 +00:00
Maintain watched file names for CLI collision checks
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.
This commit is contained in:
1 parent
78ebc2d298
commit
2a65fe0117
35 files changed
+5440
-28
No files matched your search
@@ -0,0 +1,121 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user