openpetswithchatandmcp/apps/desktop/tests/plugin-sdk-bridge-runtime-state.test.ts
2026-06-18 19:45:41 +00:00

144 lines
4.9 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
buildPluginInspectorState,
buildPluginRuntimePublicState,
clearPluginRuntimeState,
createPluginRuntimeState,
executePluginCommand,
executePluginMenuSelect,
notifyPluginConfigListeners,
} from "../src/plugin-sdk-bridge-runtime-state.js";
import type { PluginCommand } from "../src/plugin-sdk-types.js";
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
const pluginSdkBridgeSource = readFileSync(resolve(desktopRoot, "src/plugin-sdk-bridge.ts"), "utf8");
const pluginSdkBridgeRuntimeStateSource = readFileSync(resolve(desktopRoot, "src/plugin-sdk-bridge-runtime-state.ts"), "utf8");
assert.match(pluginSdkBridgeSource, /from "\.\/plugin-sdk-bridge-runtime-state(?:\.js)?"/, "plugin-sdk-bridge must import the extracted runtime-state seam.");
assert.match(pluginSdkBridgeRuntimeStateSource, /export function buildPluginRuntimePublicState/, "plugin-sdk bridge runtime-state seam must export public state shaping.");
assert.match(pluginSdkBridgeRuntimeStateSource, /export async function executePluginCommand/, "plugin-sdk bridge runtime-state seam must export command execution helpers.");
assert.match(pluginSdkBridgeRuntimeStateSource, /export function clearPluginRuntimeState/, "plugin-sdk bridge runtime-state seam must export runtime cleanup helpers.");
const state = createPluginRuntimeState();
state.status = { text: "Ready", tone: "success" };
state.menuItems = [{ id: "open", title: "Open" }] as unknown as typeof state.menuItems;
state.commands.set("run", {
meta: { id: "run", title: "Run" } as PluginCommand,
handler: () => undefined,
});
state.bubbles.set("bubble", {
host: {
id: "bubble",
update: async () => undefined,
dismiss: async () => undefined,
pin: async () => undefined,
unpin: async () => undefined,
},
dismissed: false,
});
state.panels.set("panel", {
id: "panel",
show: async () => undefined,
hide: async () => undefined,
postMessage: async () => undefined,
close: async () => undefined,
});
state.eventSubscriptions.set("evt", () => undefined);
state.busSubscriptions.set("bus", { topic: "topic", handler: () => undefined });
state.tickSubscriptions.set("tick", () => undefined);
state.petWindow.count = 2;
const publicState = buildPluginRuntimePublicState(state);
assert.equal(publicState.commands.length, 1);
assert.equal(publicState.menuItems?.length, 1);
assert.deepEqual(publicState.status, { text: "Ready", tone: "success" });
const inspectorState = buildPluginInspectorState(state);
assert.equal(inspectorState.activeBubbles, 1);
assert.equal(inspectorState.activePanels, 1);
assert.equal(inspectorState.eventSubscriptions, 3);
assert.equal(inspectorState.quotaCounters.petActions, 2);
const seenConfigs: string[] = [];
state.configListeners.add((config) => seenConfigs.push(String(config.mode)));
notifyPluginConfigListeners(state, { mode: "alpha" }, () => undefined);
assert.deepEqual(seenConfigs, ["alpha"]);
let ranCommand = false;
state.commands.set("exec", {
meta: { id: "exec", title: "Execute" } as PluginCommand,
handler: () => {
ranCommand = true;
},
});
await executePluginCommand({
pluginId: "plug",
state,
commandId: "exec",
releaseDelayMs: 0,
logger: () => undefined,
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(ranCommand, true);
assert.equal(state.userCommandDepth, 0);
let selectedItem: string | undefined;
state.menuHandlers.add((itemId) => {
selectedItem = itemId;
});
await executePluginMenuSelect({
pluginId: "plug",
state,
itemId: "open",
releaseDelayMs: 0,
logger: () => undefined,
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(selectedItem, "open");
assert.equal(state.userCommandDepth, 0);
let bubbleDismissed = 0;
state.bubbles.set("dismiss-me", {
host: {
id: "dismiss-me",
update: async () => undefined,
dismiss: async () => {
bubbleDismissed += 1;
},
pin: async () => undefined,
unpin: async () => undefined,
},
dismissed: false,
});
let panelClosed = 0;
state.panels.set("close-me", {
id: "close-me",
show: async () => undefined,
hide: async () => undefined,
postMessage: async () => undefined,
close: async () => {
panelClosed += 1;
},
});
state.spawnedPets.add("pet-1");
const closedPets: string[] = [];
clearPluginRuntimeState({
pluginId: "plug",
state,
closeFamiliar: async (_pluginId, petHandleId) => {
closedPets.push(petHandleId);
},
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(state.commands.size, 0);
assert.equal(state.bubbles.size, 0);
assert.equal(state.panels.size, 0);
assert.deepEqual(closedPets, ["pet-1"]);
assert.equal(bubbleDismissed, 1);
assert.equal(panelClosed, 1);
console.error("Plugin SDK bridge runtime-state validation passed.");