mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 03:15:46 -06:00
adfb59c19b
* feat(mac): add dashboard gateway switching * feat(ui): add dashboard gateway picker * docs(mac): document dashboard gateway picker * fix(mac): harden gateway switching after review * fix(ui): refresh gateway picker snapshots * fix(mac): carry TLS pins through promotion and serialize gateway switches * style(ui): satisfy lint rules in gateway picker files * fix(ui): tolerate absent context in gateway picker pane props * chore(ci): refresh generated inventories * style(mac): satisfy Swift CI checks * refactor(mac): drop dead GatewayEndpointStore.requireConfig * perf(ui): own gateway capability in the chat chunk * chore(ui): keep gateway capability factory module-private
115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const EVENT = "openclaw:native-gateways-changed";
|
|
const snapshot = {
|
|
gateways: [
|
|
{
|
|
id: "primary",
|
|
name: "Local Gateway",
|
|
kind: "local" as const,
|
|
isPrimary: true,
|
|
canPromote: false,
|
|
health: "ok" as const,
|
|
},
|
|
{
|
|
id: "profile:studio",
|
|
name: "Studio",
|
|
kind: "remote" as const,
|
|
isPrimary: false,
|
|
canPromote: true,
|
|
health: "unknown" as const,
|
|
},
|
|
],
|
|
currentId: "primary",
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
});
|
|
|
|
afterEach(() => {
|
|
Reflect.deleteProperty(window, "__OPENCLAW_NATIVE_GATEWAYS__");
|
|
vi.unstubAllGlobals();
|
|
Reflect.deleteProperty(window, "webkit");
|
|
});
|
|
|
|
function installBridge() {
|
|
const postMessage = vi.fn();
|
|
vi.stubGlobal("webkit", { messageHandlers: { openclawGateways: { postMessage } } });
|
|
return postMessage;
|
|
}
|
|
|
|
describe("native gateways", () => {
|
|
it("returns null without the WebKit bridge", async () => {
|
|
const { nativeGatewaysCapability } = await import("./native-gateways.runtime.ts");
|
|
|
|
expect(nativeGatewaysCapability()).toBeNull();
|
|
});
|
|
|
|
it("initializes from the native global and posts actions", async () => {
|
|
const postMessage = installBridge();
|
|
Object.assign(window, { __OPENCLAW_NATIVE_GATEWAYS__: snapshot });
|
|
const { nativeGatewaysCapability } = await import("./native-gateways.runtime.ts");
|
|
const capability = nativeGatewaysCapability();
|
|
|
|
expect(capability?.snapshot).toEqual(snapshot);
|
|
capability?.select("profile:studio");
|
|
capability?.openWindow("profile:studio");
|
|
capability?.setPrimary("profile:studio");
|
|
capability?.openSettings();
|
|
expect(postMessage.mock.calls.map(([message]) => message)).toEqual([
|
|
{ type: "select", id: "profile:studio" },
|
|
{ type: "open-window", id: "profile:studio" },
|
|
{ type: "set-primary", id: "profile:studio" },
|
|
{ type: "open-settings" },
|
|
]);
|
|
});
|
|
|
|
it("updates from events and stops notifying after unsubscribe", async () => {
|
|
installBridge();
|
|
const { nativeGatewaysCapability } = await import("./native-gateways.runtime.ts");
|
|
const capability = nativeGatewaysCapability();
|
|
const listener = vi.fn();
|
|
const unsubscribe = capability?.subscribe(listener);
|
|
window.dispatchEvent(new CustomEvent(EVENT, { detail: snapshot }));
|
|
expect(capability?.snapshot).toEqual(snapshot);
|
|
expect(listener).toHaveBeenCalledWith(snapshot);
|
|
unsubscribe?.();
|
|
listener.mockClear();
|
|
window.dispatchEvent(
|
|
new CustomEvent(EVENT, { detail: { ...snapshot, currentId: "profile:studio" } }),
|
|
);
|
|
expect(listener).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("reads the latest global when attached lazily, then publishes native updates", async () => {
|
|
installBridge();
|
|
const attachedSnapshot = { ...snapshot, currentId: "profile:studio" };
|
|
Object.assign(window, { __OPENCLAW_NATIVE_GATEWAYS__: attachedSnapshot });
|
|
const { nativeGatewaysCapability } = await import("./native-gateways.runtime.ts");
|
|
const capability = nativeGatewaysCapability();
|
|
|
|
expect(capability?.snapshot).toEqual(attachedSnapshot);
|
|
|
|
const listener = vi.fn();
|
|
const unsubscribe = capability?.subscribe(listener);
|
|
window.dispatchEvent(new CustomEvent(EVENT, { detail: snapshot }));
|
|
expect(listener).toHaveBeenCalledWith(snapshot);
|
|
unsubscribe?.();
|
|
});
|
|
|
|
it("creates the app-lifetime singleton only once", async () => {
|
|
installBridge();
|
|
Object.assign(window, { __OPENCLAW_NATIVE_GATEWAYS__: snapshot });
|
|
const { nativeGatewaysCapability } = await import("./native-gateways.runtime.ts");
|
|
|
|
const first = nativeGatewaysCapability();
|
|
const second = nativeGatewaysCapability();
|
|
|
|
expect(first).not.toBeNull();
|
|
expect(second).toBe(first);
|
|
});
|
|
});
|