mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-01 16:27:07 +00:00
Complete WebApp P2P local preparation
This commit is contained in:
@@ -146,6 +146,13 @@ export class WebAppRuntime {
|
||||
return this.paneHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import local files and complete the readiness boundary needed by optional P2P.
|
||||
*
|
||||
* An unconfigured central remote cannot use the normal offline-scan path, so
|
||||
* the explicit WebApp scan completes the same post-scan finalisation without
|
||||
* treating the central remote as configured.
|
||||
*/
|
||||
async scanLocalFiles(): Promise<boolean> {
|
||||
const core = this.core;
|
||||
const fileAccess = this.platformServiceModules?.vaultAccess;
|
||||
@@ -171,7 +178,17 @@ export class WebAppRuntime {
|
||||
this.addLog(`Failed to import ${path}: ${String(error)}`, LOG_LEVEL_NOTICE, "scan");
|
||||
}
|
||||
}
|
||||
return succeeded;
|
||||
if (!succeeded || core.services.appLifecycle.isReady()) {
|
||||
return succeeded;
|
||||
}
|
||||
if (!(await core.services.databaseEvents.onDatabaseInitialised(false))) {
|
||||
return false;
|
||||
}
|
||||
if (!(await core.services.fileProcessing.commitPendingFileEvents())) {
|
||||
return false;
|
||||
}
|
||||
core.services.appLifecycle.markIsReady();
|
||||
return true;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
|
||||
@@ -15,13 +15,23 @@ const runtimeMocks = vi.hoisted(() => {
|
||||
const clearCache = vi.fn();
|
||||
const collectFilesOnStorage = vi.fn();
|
||||
const updateToDatabase = vi.fn();
|
||||
const applicationReady = { value: false };
|
||||
const isReady = vi.fn(() => applicationReady.value);
|
||||
const markIsReady = vi.fn(() => {
|
||||
applicationReady.value = true;
|
||||
});
|
||||
const onDatabaseInitialised = vi.fn();
|
||||
const commitPendingFileEvents = vi.fn();
|
||||
const p2p = {
|
||||
replicator: {},
|
||||
};
|
||||
|
||||
const serviceHub = {
|
||||
API: { addLog },
|
||||
appLifecycle: { isReady, markIsReady },
|
||||
control: { onLoad, onReady, onUnload },
|
||||
databaseEvents: { onDatabaseInitialised },
|
||||
fileProcessing: { commitPendingFileEvents },
|
||||
setting: { currentSettings },
|
||||
vault: { scanVault },
|
||||
};
|
||||
@@ -40,12 +50,17 @@ const runtimeMocks = vi.hoisted(() => {
|
||||
|
||||
return {
|
||||
addLog,
|
||||
applicationReady,
|
||||
options: undefined as LiveSyncBrowserServiceHubOptions<never> | undefined,
|
||||
clearCache,
|
||||
cleanup,
|
||||
collectFilesOnStorage,
|
||||
commitPendingFileEvents,
|
||||
currentSettings,
|
||||
getFiles,
|
||||
isReady,
|
||||
markIsReady,
|
||||
onDatabaseInitialised,
|
||||
onLoad,
|
||||
onReady,
|
||||
onUnload,
|
||||
@@ -125,9 +140,12 @@ async function waitForMicrotasks(): Promise<void> {
|
||||
describe("WebAppRuntime lifecycle", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
runtimeMocks.applicationReady.value = false;
|
||||
runtimeMocks.options = undefined;
|
||||
runtimeMocks.commitPendingFileEvents.mockResolvedValue(true);
|
||||
runtimeMocks.currentSettings.mockReturnValue(unconfiguredSettings);
|
||||
runtimeMocks.getFiles.mockResolvedValue([{ path: "one.md" }]);
|
||||
runtimeMocks.onDatabaseInitialised.mockResolvedValue(true);
|
||||
runtimeMocks.onLoad.mockResolvedValue(true);
|
||||
runtimeMocks.onReady.mockResolvedValue(undefined);
|
||||
runtimeMocks.onUnload.mockResolvedValue(undefined);
|
||||
@@ -174,7 +192,7 @@ describe("WebAppRuntime lifecycle", () => {
|
||||
expect(runtimeMocks.onUnload).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("imports local files for optional P2P while the main remote remains unconfigured", async () => {
|
||||
it("finalises application readiness after importing local files for optional P2P", async () => {
|
||||
const runtime = new WebAppRuntime(createRootHandle());
|
||||
await runtime.start();
|
||||
vi.clearAllMocks();
|
||||
@@ -203,11 +221,93 @@ describe("WebAppRuntime lifecycle", () => {
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(runtimeMocks.updateToDatabase).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.onDatabaseInitialised).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.commitPendingFileEvents).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.markIsReady).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.updateToDatabase.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
runtimeMocks.onDatabaseInitialised.mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(runtimeMocks.onDatabaseInitialised.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
runtimeMocks.commitPendingFileEvents.mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(runtimeMocks.commitPendingFileEvents.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
runtimeMocks.markIsReady.mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(runtimeMocks.scanVault).not.toHaveBeenCalled();
|
||||
expect(runtimeMocks.currentSettings()).toBe(unconfiguredSettings);
|
||||
expect(unconfiguredSettings.isConfigured).toBe(false);
|
||||
});
|
||||
|
||||
it("does not publish readiness after a partial local-file import", async () => {
|
||||
const runtime = new WebAppRuntime(createRootHandle());
|
||||
await runtime.start();
|
||||
vi.clearAllMocks();
|
||||
runtimeMocks.collectFilesOnStorage.mockResolvedValue({
|
||||
storageFileNameMap: {
|
||||
"one.md": {
|
||||
path: "one.md",
|
||||
stat: { ctime: 1, mtime: 1, size: 3, type: "file" },
|
||||
},
|
||||
"two.md": {
|
||||
path: "two.md",
|
||||
stat: { ctime: 2, mtime: 2, size: 3, type: "file" },
|
||||
},
|
||||
},
|
||||
storageFileNames: ["one.md", "two.md"],
|
||||
storageFileNameCI2CS: { "one.md": "one.md", "two.md": "two.md" },
|
||||
});
|
||||
runtimeMocks.updateToDatabase
|
||||
.mockRejectedValueOnce(new Error("one.md could not be imported"))
|
||||
.mockResolvedValueOnce(undefined);
|
||||
|
||||
await expect(runtime.scanLocalFiles()).resolves.toBe(false);
|
||||
|
||||
expect(runtimeMocks.updateToDatabase).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeMocks.onDatabaseInitialised).not.toHaveBeenCalled();
|
||||
expect(runtimeMocks.commitPendingFileEvents).not.toHaveBeenCalled();
|
||||
expect(runtimeMocks.markIsReady).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not publish readiness when database initialisation rejects manual preparation", async () => {
|
||||
const runtime = new WebAppRuntime(createRootHandle());
|
||||
await runtime.start();
|
||||
vi.clearAllMocks();
|
||||
runtimeMocks.onDatabaseInitialised.mockResolvedValue(false);
|
||||
|
||||
await expect(runtime.scanLocalFiles()).resolves.toBe(false);
|
||||
|
||||
expect(runtimeMocks.onDatabaseInitialised).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.commitPendingFileEvents).not.toHaveBeenCalled();
|
||||
expect(runtimeMocks.markIsReady).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not publish readiness when pending file events cannot be committed", async () => {
|
||||
const runtime = new WebAppRuntime(createRootHandle());
|
||||
await runtime.start();
|
||||
vi.clearAllMocks();
|
||||
runtimeMocks.commitPendingFileEvents.mockResolvedValue(false);
|
||||
|
||||
await expect(runtime.scanLocalFiles()).resolves.toBe(false);
|
||||
|
||||
expect(runtimeMocks.onDatabaseInitialised).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.commitPendingFileEvents).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.markIsReady).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not repeat readiness finalisation after a successful manual scan", async () => {
|
||||
const runtime = new WebAppRuntime(createRootHandle());
|
||||
await runtime.start();
|
||||
vi.clearAllMocks();
|
||||
|
||||
await expect(runtime.scanLocalFiles()).resolves.toBe(true);
|
||||
await expect(runtime.scanLocalFiles()).resolves.toBe(true);
|
||||
|
||||
expect(runtimeMocks.updateToDatabase).toHaveBeenCalledTimes(2);
|
||||
expect(runtimeMocks.onDatabaseInitialised).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.commitPendingFileEvents).toHaveBeenCalledOnce();
|
||||
expect(runtimeMocks.markIsReady).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("rejects a failed core start after cleaning up the partial runtime", async () => {
|
||||
runtimeMocks.onLoad.mockResolvedValue(false);
|
||||
const reportStatus = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user