144 lines
4.9 KiB
TypeScript
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.");
|