mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-12 04:12:30 +00:00
refactor: consume Commonlib as a package
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import {
|
||||
encodeQR,
|
||||
encodeSettingsToQRCodeData,
|
||||
OutputFormat,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
import { EVENT_REQUEST_SHOW_SETUP_QR } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { SetupFeatureHost } from "./types";
|
||||
|
||||
export async function encodeSetupSettingsAsQR(host: SetupFeatureHost) {
|
||||
const settingString = encodeSettingsToQRCodeData(host.services.setting.currentSettings());
|
||||
const result = encodeQR(settingString, OutputFormat.SVG);
|
||||
if (result === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeof result === "string") {
|
||||
const msg = host.services.context.translate("Setup.QRCode", { qr_image: result });
|
||||
await host.services.UI.confirm.confirmWithMessage("Settings QR Code", msg, ["OK"], "OK");
|
||||
return result;
|
||||
} else {
|
||||
// Multi-page QR code
|
||||
let currentIndex = 0;
|
||||
while (currentIndex < result.total) {
|
||||
const msg = `The setting is too large for a single QR code.
|
||||
We are using the aggregator to combine multiple QR codes.
|
||||
Your settings will not be sent to any server; they will be processed only on your device.
|
||||
Please scan this QR code with your mobile's camera, and open the page in your browser.
|
||||
After all parts are collected, the page will navigate you back to Obsidian with the aggregated settings.
|
||||
|
||||
Progress: ${currentIndex + 1} / ${result.total}
|
||||
${result.parts[currentIndex]}`;
|
||||
|
||||
const buttons = [];
|
||||
if (currentIndex > 0) buttons.push("Back");
|
||||
if (currentIndex < result.total - 1) {
|
||||
buttons.push("Next");
|
||||
buttons.push("Cancel");
|
||||
} else {
|
||||
buttons.push("Done");
|
||||
}
|
||||
|
||||
const choice = await host.services.UI.confirm.confirmWithMessage(
|
||||
"Settings QR Code (Aggregated)",
|
||||
msg,
|
||||
buttons,
|
||||
buttons[buttons.indexOf("Next") !== -1 ? buttons.indexOf("Next") : buttons.indexOf("Done")]
|
||||
);
|
||||
|
||||
if (choice === "Next") {
|
||||
currentIndex++;
|
||||
} else if (choice === "Back") {
|
||||
currentIndex--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result.parts[0]; // Return the first one for compatibility
|
||||
}
|
||||
}
|
||||
|
||||
export function useSetupQRCodeFeature(host: NecessaryServices<"API" | "UI" | "setting" | "appLifecycle", never>) {
|
||||
host.services.appLifecycle.onLoaded.addHandler(() => {
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-setting-qr",
|
||||
name: "Show settings as a QR code",
|
||||
callback: () => fireAndForget(encodeSetupSettingsAsQR(host)),
|
||||
});
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_SHOW_SETUP_QR, () =>
|
||||
fireAndForget(() => encodeSetupSettingsAsQR(host))
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { EVENT_REQUEST_SHOW_SETUP_QR } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||
import { encodeSetupSettingsAsQR, useSetupQRCodeFeature } from "./qrCode";
|
||||
import { encodeQR, encodeSettingsToQRCodeData } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
|
||||
vi.mock("@vrtmrz/livesync-commonlib/compat/API/processSetting", () => {
|
||||
return {
|
||||
encodeQR: vi.fn(),
|
||||
encodeSettingsToQRCodeData: vi.fn(),
|
||||
OutputFormat: {
|
||||
SVG: "svg",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("setupObsidian/qrCode", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("encodeSetupSettingsAsQR should return empty string when QR generation fails", async () => {
|
||||
const confirmWithMessage = vi.fn();
|
||||
const host = {
|
||||
services: {
|
||||
context: createServiceContext(),
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ any: "settings" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
confirmWithMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
vi.mocked(encodeSettingsToQRCodeData).mockReturnValue("encoded-settings");
|
||||
vi.mocked(encodeQR).mockReturnValue("");
|
||||
|
||||
const result = await encodeSetupSettingsAsQR(host);
|
||||
|
||||
expect(result).toBe("");
|
||||
expect(confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("encodeSetupSettingsAsQR should show confirm dialog when QR is generated", async () => {
|
||||
const confirmWithMessage = vi.fn(() => true);
|
||||
const translate = vi.fn(() => "qr-message");
|
||||
const host = {
|
||||
services: {
|
||||
context: createServiceContext({ translate }),
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ any: "settings" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
confirmWithMessage,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
vi.mocked(encodeSettingsToQRCodeData).mockReturnValue("encoded-settings");
|
||||
vi.mocked(encodeQR).mockReturnValue("<svg/>");
|
||||
|
||||
const result = await encodeSetupSettingsAsQR(host);
|
||||
|
||||
expect(result).toBe("<svg/>");
|
||||
expect(translate).toHaveBeenCalledWith("Setup.QRCode", { qr_image: "<svg/>" });
|
||||
expect(confirmWithMessage).toHaveBeenCalledWith("Settings QR Code", "qr-message", ["OK"], "OK");
|
||||
});
|
||||
|
||||
it("useSetupQRCodeFeature should register onLoaded handler that wires command and event", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
const context = createServiceContext();
|
||||
const onEventSpy = vi.spyOn(context.events, "onEvent");
|
||||
|
||||
const host = {
|
||||
services: {
|
||||
context,
|
||||
API: {
|
||||
addCommand,
|
||||
},
|
||||
appLifecycle: {
|
||||
onLoaded: {
|
||||
addHandler,
|
||||
},
|
||||
},
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ any: "settings" })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
confirmWithMessage: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
useSetupQRCodeFeature(host);
|
||||
expect(addHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
const loadedHandler = addHandler.mock.calls[0][0] as () => Promise<boolean>;
|
||||
await loadedHandler();
|
||||
|
||||
expect(addCommand).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "livesync-setting-qr",
|
||||
name: "Show settings as a QR code",
|
||||
})
|
||||
);
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_SHOW_SETUP_QR, expect.any(Function));
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
import { type SetupManager, UserMode } from "@/modules/features/SetupManager";
|
||||
import type { SetupFeatureHost } from "@lib/serviceFeatures/setupObsidian/types";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@lib/events/coreEvents";
|
||||
import { eventHub } from "@lib/hub/hub";
|
||||
import { fireAndForget } from "@lib/common/utils";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import type { SetupFeatureHost } from "@/serviceFeatures/setupObsidian/types";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
|
||||
export async function openSetupURI(setupManager: SetupManager) {
|
||||
await setupManager.onUseSetupURI(UserMode.Unknown);
|
||||
@@ -24,8 +23,10 @@ export function useSetupManagerHandlersFeature(
|
||||
callback: () => fireAndForget(openSetupURI(setupManager)),
|
||||
});
|
||||
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () => fireAndForget(() => openSetupURI(setupManager)));
|
||||
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () =>
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () =>
|
||||
fireAndForget(() => openSetupURI(setupManager))
|
||||
);
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () =>
|
||||
fireAndForget(() => openP2PSettings(host, setupManager))
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { eventHub } from "@lib/hub/hub";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@lib/events/coreEvents";
|
||||
import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { openP2PSettings, openSetupURI, useSetupManagerHandlersFeature } from "./setupManagerHandlers";
|
||||
|
||||
vi.mock("@/modules/features/SetupManager", () => {
|
||||
@@ -47,10 +46,11 @@ describe("setupObsidian/setupManagerHandlers", () => {
|
||||
it("useSetupManagerHandlersFeature should register onLoaded handler that wires command and events", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
const onEventSpy = vi.spyOn(eventHub, "onEvent");
|
||||
const events = { onEvent: vi.fn() };
|
||||
|
||||
const host = {
|
||||
services: {
|
||||
context: { events },
|
||||
API: {
|
||||
addCommand,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ describe("setupObsidian/setupManagerHandlers", () => {
|
||||
name: "Use the copied setup URI (Formerly Open setup URI)",
|
||||
})
|
||||
);
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_SETUP_URI, expect.any(Function));
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_P2P_SETTINGS, expect.any(Function));
|
||||
expect(events.onEvent).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_SETUP_URI, expect.any(Function));
|
||||
expect(events.onEvent).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_P2P_SETTINGS, expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@lib/common/types";
|
||||
import type { LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
|
||||
import type { SetupFeatureHost } from "@lib/serviceFeatures/setupObsidian/types";
|
||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import type { SetupFeatureHost } from "@/serviceFeatures/setupObsidian/types";
|
||||
import { configURIBase } from "@/common/types";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { type SetupManager, UserMode } from "@/modules/features/SetupManager";
|
||||
|
||||
async function handleSetupProtocol(setupManager: SetupManager, conf: Record<string, string>) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { LOG_LEVEL_NOTICE, type ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { LogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { createInstanceLogFunction } from "@vrtmrz/livesync-commonlib/compat/services/lib/logUtils";
|
||||
import { encodeSettingsToSetupURI } from "@vrtmrz/livesync-commonlib/compat/API/processSetting";
|
||||
import { EVENT_REQUEST_COPY_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import type { SetupFeatureHost } from "./types";
|
||||
|
||||
export async function askEncryptingPassphrase(host: SetupFeatureHost): Promise<string | false> {
|
||||
return await host.services.UI.confirm.askString(
|
||||
"Encrypt your settings",
|
||||
"The passphrase to encrypt the setup URI",
|
||||
"",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
export async function copySetupURI(host: SetupFeatureHost, log: LogFunction, stripExtra = true) {
|
||||
const encryptingPassphrase = await askEncryptingPassphrase(host);
|
||||
if (encryptingPassphrase === false) return;
|
||||
const encryptedURI = await encodeSettingsToSetupURI(
|
||||
host.services.setting.currentSettings(),
|
||||
encryptingPassphrase,
|
||||
[...((stripExtra ? ["pluginSyncExtendedSetting"] : []) as (keyof ObsidianLiveSyncSettings)[])],
|
||||
true
|
||||
);
|
||||
if (await host.services.UI.promptCopyToClipboard("Setup URI", encryptedURI)) {
|
||||
log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
export async function copySetupURIFull(host: SetupFeatureHost, log: LogFunction) {
|
||||
const encryptingPassphrase = await askEncryptingPassphrase(host);
|
||||
if (encryptingPassphrase === false) return;
|
||||
const encryptedURI = await encodeSettingsToSetupURI(
|
||||
host.services.setting.currentSettings(),
|
||||
encryptingPassphrase,
|
||||
[],
|
||||
false
|
||||
);
|
||||
if (await host.services.UI.promptCopyToClipboard("Setup URI", encryptedURI)) {
|
||||
log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
export function useSetupURIFeature(host: NecessaryServices<"API" | "UI" | "setting" | "appLifecycle", never>) {
|
||||
const log = createInstanceLogFunction("SF:SetupURI", host.services.API);
|
||||
host.services.appLifecycle.onLoaded.addHandler(() => {
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-copysetupuri",
|
||||
name: "Copy settings as a new setup URI",
|
||||
callback: () => fireAndForget(copySetupURI(host, log)),
|
||||
});
|
||||
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-copysetupuri-short",
|
||||
name: "Copy settings as a new setup URI (With customization sync)",
|
||||
callback: () => fireAndForget(copySetupURI(host, log, false)),
|
||||
});
|
||||
|
||||
host.services.API.addCommand({
|
||||
id: "livesync-copysetupurifull",
|
||||
name: "Copy settings as a new setup URI (Full)",
|
||||
callback: () => fireAndForget(copySetupURIFull(host, log)),
|
||||
});
|
||||
|
||||
host.services.context.events.onEvent(EVENT_REQUEST_COPY_SETUP_URI, () =>
|
||||
fireAndForget(() => copySetupURI(host, log))
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { EVENT_REQUEST_COPY_SETUP_URI } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||
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(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("setupObsidian/setupUri", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("askEncryptingPassphrase should delegate to confirm.askString", async () => {
|
||||
const askString = vi.fn(() => "secret");
|
||||
const host = {
|
||||
services: {
|
||||
UI: {
|
||||
confirm: {
|
||||
askString,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
const result = await askEncryptingPassphrase(host);
|
||||
expect(result).toBe("secret");
|
||||
expect(askString).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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);
|
||||
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => currentSettings),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass"),
|
||||
},
|
||||
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(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://value");
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("copySetupURIFull should encode with full mode", async () => {
|
||||
const promptCopyToClipboard = vi.fn(() => true);
|
||||
const currentSettings = { pluginSyncExtendedSetting: true, x: 1 };
|
||||
const host = {
|
||||
services: {
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => currentSettings),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass-full"),
|
||||
},
|
||||
promptCopyToClipboard,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const log = vi.fn();
|
||||
vi.mocked(encodeSettingsToSetupURI).mockResolvedValue("uri://full" as any);
|
||||
|
||||
await copySetupURIFull(host, log);
|
||||
|
||||
expect(encodeSettingsToSetupURI).toHaveBeenCalledWith(currentSettings, "pass-full", [], false);
|
||||
expect(promptCopyToClipboard).toHaveBeenCalledWith("Setup URI", "uri://full");
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("useSetupURIFeature should register onLoaded handler that wires commands and event", async () => {
|
||||
const addHandler = vi.fn();
|
||||
const addCommand = vi.fn();
|
||||
const context = createServiceContext();
|
||||
const onEventSpy = vi.spyOn(context.events, "onEvent");
|
||||
|
||||
const host = {
|
||||
services: {
|
||||
context,
|
||||
API: {
|
||||
addCommand,
|
||||
addLog: vi.fn(),
|
||||
},
|
||||
appLifecycle: {
|
||||
onLoaded: {
|
||||
addHandler,
|
||||
},
|
||||
},
|
||||
setting: {
|
||||
currentSettings: vi.fn(() => ({ x: 1 })),
|
||||
},
|
||||
UI: {
|
||||
confirm: {
|
||||
askString: vi.fn(() => "pass"),
|
||||
},
|
||||
promptCopyToClipboard: vi.fn(() => true),
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
useSetupURIFeature(host);
|
||||
expect(addHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
const loadedHandler = addHandler.mock.calls[0][0] as () => Promise<boolean>;
|
||||
await loadedHandler();
|
||||
|
||||
expect(addCommand).toHaveBeenCalledTimes(3);
|
||||
expect(addCommand).toHaveBeenCalledWith(expect.objectContaining({ id: "livesync-copysetupuri" }));
|
||||
expect(addCommand).toHaveBeenCalledWith(expect.objectContaining({ id: "livesync-copysetupuri-short" }));
|
||||
expect(addCommand).toHaveBeenCalledWith(expect.objectContaining({ id: "livesync-copysetupurifull" }));
|
||||
expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_COPY_SETUP_URI, expect.any(Function));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { NecessaryServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
|
||||
export type SetupFeatureHost = NecessaryServices<"API" | "UI" | "setting", never>;
|
||||
Reference in New Issue
Block a user