Reuse scoped content and progress operations

This commit is contained in:
vorotamoroz committed 2026-10-29 14:56:56 +00:00
1 parent 28bc6e87a1
commit cfdc698775
16 files changed
+1596 -132

No files matched your search

@@ -0,0 +1,143 @@
import { describe, expect, it, vi } from "vitest";
import {
type DocumentID,
type FilePath,
type FilePathWithPrefix,
type MetaEntry,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
vi.mock("@/deps.ts", () => ({}));
vi.mock("@/features/HiddenFileCommon/JsonResolveModal.ts", () => ({
JsonResolveModal: class JsonResolveModal {},
}));
vi.mock("@/features/LiveSyncCommands.ts", () => ({
LiveSyncCommands: class LiveSyncCommands {
plugin!: { app: unknown };
core!: { services: unknown; settings: unknown };
get app() {
return this.plugin.app;
}
get services() {
return this.core.services;
}
get settings() {
return this.core.settings;
}
},
}));
vi.mock("./configureHiddenFileSyncMode.ts", () => ({
configureHiddenFileSyncMode: vi.fn(),
}));
import { HiddenFileSync } from "./CmdHiddenFileSync.ts";
function createProgress(events: string[] = []) {
return {
log: vi.fn((message: string) => {
if (/^(?:\d+\/\d+|Checking \d+\/\d+)$/.test(message)) events.push(message);
}),
once: vi.fn(),
done: vi.fn(),
};
}
function createStoragePaths(count: number): FilePath[] {
return Array.from({ length: count }, (_, index) => `.obsidian/plugins/example/${index}.json` as FilePath);
}
function createDatabaseEntries(count: number): MetaEntry[] {
return Array.from(
{ length: count },
(_, index) =>
({
_id: `i:.obsidian/plugins/example/${index}.json` as DocumentID,
_rev: "1-test",
path: `i:.obsidian/plugins/example/${index}.json` as FilePathWithPrefix,
ctime: 1,
mtime: 1,
size: 0,
type: "plain",
datatype: "plain",
children: [],
eden: {},
deleted: false,
}) as MetaEntry
);
}
function createHiddenFileSync(count: number, events: string[] = []) {
const progress = createProgress(events);
const statHidden = vi.fn(async () => {
events.push(`stat:${statHidden.mock.calls.length}`);
return { mtime: 1 };
});
const hiddenFileSync = Object.create(HiddenFileSync.prototype) as HiddenFileSync;
Object.assign(hiddenFileSync, {
_progress: vi.fn(() => progress),
_log: vi.fn(),
_fileInfoLastProcessed: new Map(),
_databaseInfoLastProcessed: new Map(),
scanInternalFileNames: vi.fn(async () => createStoragePaths(count)),
getAllDatabaseFiles: vi.fn(async () => createDatabaseEntries(count)),
getPath: vi.fn((file: MetaEntry) => file.path),
updateLastProcessed: vi.fn(),
scanAllStorageChanges: vi.fn(async () => undefined),
scanAllDatabaseChanges: vi.fn(async () => undefined),
resetLastProcessedDatabase: vi.fn(),
resetLastProcessedFile: vi.fn(),
useStorageFiles: vi.fn(async () => false),
useDatabaseFiles: vi.fn(async () => false),
trackStorageFileModification: vi.fn(async () => false),
trackDatabaseFileModification: vi.fn(async () => false),
core: { storageAccess: { statHidden } },
});
return { hiddenFileSync, progress, statHidden };
}
describe("HiddenFileSync attempt progress", () => {
it("reports applyOfflineChanges attempts at 1 and each 25 items, without a final partial count", async () => {
const events: string[] = [];
const { hiddenFileSync, progress } = createHiddenFileSync(52, events);
await hiddenFileSync.applyOfflineChanges(false);
expect(progress.log.mock.calls.map(([message]) => message).filter((message) => /^\d+\/\d+$/.test(message))).toEqual([
"1/52",
"26/52",
"51/52",
]);
expect(events.indexOf("51/52")).toBeLessThan(events.indexOf("stat:1"));
});
it("reports rebuildMerging attempts at 1 and each 100 items, without a final partial count", async () => {
const events: string[] = [];
const { hiddenFileSync, progress } = createHiddenFileSync(202, events);
await hiddenFileSync.rebuildMerging(false);
expect(progress.log.mock.calls.map(([message]) => message).filter((message) => message.startsWith("Checking "))).toEqual([
"Checking 1/202",
"Checking 101/202",
"Checking 201/202",
]);
expect(events.indexOf("Checking 101/202")).toBeLessThan(events.indexOf("stat:101"));
});
it("preserves stat failures from applyOfflineChanges", async () => {
const { hiddenFileSync, progress, statHidden } = createHiddenFileSync(1);
const error = new Error("stat failed");
statHidden.mockRejectedValueOnce(error);
await expect(hiddenFileSync.applyOfflineChanges(false)).rejects.toBe(error);
expect(progress.done).not.toHaveBeenCalled();
});
it("preserves stat failures from rebuildMerging", async () => {
const { hiddenFileSync, progress, statHidden } = createHiddenFileSync(1);
const error = new Error("stat failed");
statHidden.mockRejectedValueOnce(error);
await expect(hiddenFileSync.rebuildMerging(false)).rejects.toBe(error);
expect(progress.done).not.toHaveBeenCalled();
});
});