mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-25 02:23:22 +00:00
Prepare time-bound Setup URI integration for Commonlib release
Add Time-bound and Compatible generation to the Obsidian dialogue, browser peer check, and setup tools, with boundary and compatibility coverage. Show the fixed window end before sharing a Time-bound URI. This review branch depends on unpublished @vrtmrz/livesync-commonlib 0.1.32-next.0. The root npm pin and Deno lockfile remain unchanged until that candidate is available; a fresh install of this commit is not yet expected to build.
This commit is contained in:
1 parent
2c2b9c90e4
commit
3a478cce3f
23 files changed
+1361
-119
No files matched your search
@@ -1,119 +1,203 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import { EVENT_REQUEST_COPY_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
||||
import {
|
||||
encodeTimeBoundSetupURI,
|
||||
getTimeBoundSetupURIUsableUntil,
|
||||
isTimeBoundSetupURIUsableNow,
|
||||
} from "@vrtmrz/livesync-commonlib/setup-uri";
|
||||
import { askEncryptingPassphrase, copySetupURI, copySetupURIFull, useSetupURIFeature } from "./setupUri";
|
||||
import { encodeSettingsToSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
|
||||
return {
|
||||
encodeSettingsToSetupURI: vi.fn(),
|
||||
};
|
||||
});
|
||||
vi.mock("@vrtmrz/livesync-commonlib/setup-uri", () => ({
|
||||
encodeTimeBoundSetupURI: vi.fn(),
|
||||
getTimeBoundSetupURIUsableUntil: vi.fn(),
|
||||
isTimeBoundSetupURIUsableNow: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("setupObsidian/setupUri", () => {
|
||||
const usableUntil = Date.parse("2026-10-01T00:00:00Z");
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(getTimeBoundSetupURIUsableUntil).mockReturnValue(usableUntil);
|
||||
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({ uri: "obsidian://setup-time-bound ", usableUntil });
|
||||
vi.mocked(isTimeBoundSetupURIUsableNow).mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("askEncryptingPassphrase should delegate to confirm.askString", async () => {
|
||||
const askString = vi.fn(() => "secret");
|
||||
const host = {
|
||||
services: {
|
||||
UI: {
|
||||
confirm: {
|
||||
askString,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
it("uses the existing password prompt", async () => {
|
||||
const askString = vi.fn(async () => "secret");
|
||||
const host = { services: { UI: { confirm: { askString } } } } as any;
|
||||
|
||||
const result = await askEncryptingPassphrase(host);
|
||||
expect(result).toBe("secret");
|
||||
expect(askString).toHaveBeenCalled();
|
||||
await expect(askEncryptingPassphrase(host)).resolves.toBe("secret");
|
||||
expect(askString).toHaveBeenCalledWith(
|
||||
"Encrypt your settings",
|
||||
"The passphrase to encrypt the setup URI",
|
||||
"",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("copySetupURI should return early when user cancels passphrase", async () => {
|
||||
const promptCopyToClipboard = vi.fn();
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ foo: "bar" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => false),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
|
||||
await copySetupURI(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).not.toHaveBeenCalled();
|
||||
expect(promptCopyToClipboard).not.toHaveBeenCalled();
|
||||
expect(log).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("copySetupURI should encode with short mode by default", async () => {
|
||||
const promptCopyToClipboard = vi.fn(() => true);
|
||||
it("shows the exact Time-bound end at selection and uses it by default", async () => {
|
||||
const askSelectStringDialogue = vi.fn(async () => "Time-bound");
|
||||
const promptCopyToClipboard = vi.fn(async () => true);
|
||||
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => currentSettings),
|
||||
},
|
||||
setting: { currentSettings: vi.fn(() => currentSettings) },
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass"),
|
||||
},
|
||||
confirm: { askString: vi.fn(async () => "pass"), askSelectStringDialogue },
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://value" as any);
|
||||
|
||||
await copySetupURI(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(
|
||||
currentSettings,
|
||||
"pass",
|
||||
["pluginSyncExtendedSetting"],
|
||||
true
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledWith(
|
||||
expect.stringContaining("2026"),
|
||||
["Time-bound", "Compatible (no time limit)", "Cancel"],
|
||||
{
|
||||
title: "Setup URI availability",
|
||||
defaultAction: "Time-bound",
|
||||
}
|
||||
);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://value");
|
||||
expect(encodeTimeBoundSetupURI).toHaveBeenCalledWith(currentSettings, "pass", {
|
||||
mode: "ephemeral",
|
||||
removeProperties: ["pluginSyncExtendedSetting"],
|
||||
skipDefaultValue: true,
|
||||
});
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "obsidian://setup-time-bound ");
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("copySetupURIFull should encode with full mode", async () => {
|
||||
const promptCopyToClipboard = vi.fn(() => true);
|
||||
it("selects old-reader-compatible output without a time limit and preserves full export settings", async () => {
|
||||
const promptCopyToClipboard = vi.fn(async () => true);
|
||||
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => currentSettings),
|
||||
},
|
||||
setting: { currentSettings: vi.fn(() => currentSettings) },
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass-full"),
|
||||
askString: vi.fn(async () => "pass"),
|
||||
askSelectStringDialogue: vi.fn(async () => "Compatible (no time limit)"),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://full" as any);
|
||||
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({
|
||||
uri: "obsidian://setup-compatible ",
|
||||
usableUntil: null,
|
||||
});
|
||||
|
||||
await copySetupURIFull(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(currentSettings, "pass-full", [], false);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://full");
|
||||
expect(encodeTimeBoundSetupURI).toHaveBeenCalledWith(currentSettings, "pass", {
|
||||
mode: "persistent",
|
||||
removeProperties: [],
|
||||
skipDefaultValue: false,
|
||||
});
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "obsidian://setup-compatible ");
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps Compatible available when the clock is invalid", async () => {
|
||||
vi.mocked(getTimeBoundSetupURIUsableUntil).mockImplementation(() => {
|
||||
throw new Error("Invalid Setup URI clock");
|
||||
});
|
||||
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({ uri: "compatible", usableUntil: null });
|
||||
const askSelectStringDialogue = vi.fn(async () => "Compatible (no time limit)");
|
||||
const host = {
|
||||
services: {
|
||||
setting: { currentSettings: vi.fn(() => ({})) },
|
||||
UI: {
|
||||
confirm: { askString: vi.fn(async () => "pass"), askSelectStringDialogue },
|
||||
promptCopyToClipboard: vi.fn(async () => false),
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
await copySetupURI(host, vi.fn());
|
||||
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledWith(
|
||||
expect.stringContaining("valid device clock"),
|
||||
["Compatible (no time limit)", "Cancel"],
|
||||
expect.objectContaining({ defaultAction: "Compatible (no time limit)" })
|
||||
);
|
||||
});
|
||||
|
||||
it("does not generate after password or mode cancellation", async () => {
|
||||
const askString = vi.fn(async (): Promise<string | false> => false);
|
||||
const askSelectStringDialogue = vi.fn(async () => "Cancel");
|
||||
const promptCopyToClipboard = vi.fn();
|
||||
const host = {
|
||||
services: {
|
||||
setting: { currentSettings: vi.fn(() => ({})) },
|
||||
UI: { confirm: { askString, askSelectStringDialogue }, promptCopyToClipboard },
|
||||
},
|
||||
} as any;
|
||||
|
||||
await copySetupURI(host, vi.fn());
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
askString.mockResolvedValueOnce("pass");
|
||||
await copySetupURI(host, vi.fn());
|
||||
expect(encodeTimeBoundSetupURI).not.toHaveBeenCalled();
|
||||
expect(promptCopyToClipboard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps an empty passphrase distinct from cancelling the existing prompt", async () => {
|
||||
vi.mocked(encodeTimeBoundSetupURI).mockResolvedValue({ uri: "compatible", usableUntil: null });
|
||||
const promptCopyToClipboard = vi.fn(async () => false);
|
||||
const host = {
|
||||
services: {
|
||||
setting: { currentSettings: vi.fn(() => ({})) },
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(async () => ""),
|
||||
askSelectStringDialogue: vi.fn(async () => "Compatible (no time limit)"),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
await copySetupURI(host, vi.fn());
|
||||
|
||||
expect(encodeTimeBoundSetupURI).toHaveBeenCalledWith({}, "", expect.objectContaining({ mode: "persistent" }));
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "compatible");
|
||||
});
|
||||
|
||||
it("asks again if the window changes after selection", async () => {
|
||||
const nextUntil = usableUntil + 604_800_000;
|
||||
vi.mocked(getTimeBoundSetupURIUsableUntil).mockReturnValueOnce(usableUntil).mockReturnValueOnce(nextUntil);
|
||||
vi.mocked(encodeTimeBoundSetupURI)
|
||||
.mockResolvedValueOnce({ uri: "old-window", usableUntil: nextUntil })
|
||||
.mockResolvedValueOnce({ uri: "new-window", usableUntil: nextUntil });
|
||||
const askSelectStringDialogue = vi.fn(async () => "Time-bound");
|
||||
const promptCopyToClipboard = vi.fn(async () => false);
|
||||
const host = {
|
||||
services: {
|
||||
setting: { currentSettings: vi.fn(() => ({})) },
|
||||
UI: {
|
||||
confirm: { askString: vi.fn(async () => "pass"), askSelectStringDialogue },
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
await copySetupURI(host, vi.fn());
|
||||
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledTimes(2);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledTimes(1);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "new-window");
|
||||
});
|
||||
|
||||
it("useSetupURIFeature should register onLoaded handler that wires commands and event", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
|
||||
Reference in new issue
Block a user