99 lines
3.8 KiB
TypeScript
99 lines
3.8 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 {
|
|
validateCommandFormValuesSpec,
|
|
validateCommandSpec,
|
|
} from "../src/plugin-sdk-command-validation.js";
|
|
|
|
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const pluginSdkValidatorsSource = readFileSync(resolve(desktopRoot, "src/plugin-sdk-validators.ts"), "utf8");
|
|
const pluginSdkCommandValidationSource = readFileSync(resolve(desktopRoot, "src/plugin-sdk-command-validation.ts"), "utf8");
|
|
|
|
assert.match(pluginSdkValidatorsSource, /from "\.\/plugin-sdk-command-validation(?:\.js)?"/, "plugin-sdk-validators must import the extracted command validation seam.");
|
|
assert.match(pluginSdkCommandValidationSource, /export function validateCommandSpec/, "plugin-sdk command validation seam must export command metadata validation.");
|
|
assert.match(pluginSdkCommandValidationSource, /export function validateCommandFormValuesSpec/, "plugin-sdk command validation seam must export command form value validation.");
|
|
|
|
const helpers = {
|
|
check(ok: boolean, message: string): void {
|
|
if (!ok) throw new Error(message);
|
|
},
|
|
clampNumber(value: number, min: number, max: number): number {
|
|
if (!Number.isFinite(value)) return min;
|
|
return Math.min(Math.max(value, min), max);
|
|
},
|
|
isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
},
|
|
commandIdPattern: /^[A-Za-z0-9._:-]{1,64}$/,
|
|
namedHostIcons: new Set(["info", "check", "alert", "heart", "star", "bell", "coffee", "timer", "droplet", "sparkles", "zap", "moon", "sun", "food", "play", "pause"]),
|
|
} as const;
|
|
|
|
const command = validateCommandSpec(
|
|
{
|
|
id: "status.run",
|
|
title: "Run status",
|
|
description: "Gather a quick plugin status snapshot.",
|
|
placement: "top",
|
|
priority: 1200,
|
|
featured: true,
|
|
icon: { kind: "icon", name: "badge" } as const,
|
|
form: {
|
|
submitLabel: "Run",
|
|
fields: [
|
|
{ id: "count", type: "number", label: "Count", min: 1, max: 10, default: 3 },
|
|
{ id: "day", type: "select", label: "Day", options: [{ label: "Mon", value: "mon" }, { label: "Tue", value: "tue" }], default: "mon" },
|
|
],
|
|
},
|
|
},
|
|
(ref) => {
|
|
assert.deepEqual(ref, { kind: "icon", name: "badge" });
|
|
return { kind: "icons", name: "badge", path: "assets/icons/badge.svg" };
|
|
},
|
|
helpers,
|
|
);
|
|
|
|
assert.equal(command.id, "status.run");
|
|
assert.equal(command.priority, 1000, "Command priority should clamp to the safe range.");
|
|
assert.equal(command.form?.fields.length, 2);
|
|
assert.deepEqual(command.icon, { kind: "icon", name: "badge" });
|
|
|
|
const values = validateCommandFormValuesSpec(command.form!, {
|
|
count: "7",
|
|
day: "tue",
|
|
}, helpers);
|
|
assert.deepEqual(values, { count: 7, day: "tue" });
|
|
|
|
const multiSelectValues = validateCommandFormValuesSpec({
|
|
fields: [
|
|
{
|
|
id: "days",
|
|
type: "multiSelect",
|
|
label: "Days",
|
|
options: [{ label: "Mon", value: "mon" }, { label: "Tue", value: "tue" }],
|
|
default: ["mon"],
|
|
},
|
|
],
|
|
}, { days: ["tue"] }, helpers);
|
|
assert.deepEqual(multiSelectValues, { days: ["tue"] });
|
|
|
|
assert.throws(
|
|
() => validateCommandFormValuesSpec(command.form!, { count: 0, day: "mon" }, helpers),
|
|
/Count is too small/,
|
|
);
|
|
assert.throws(
|
|
() => validateCommandFormValuesSpec(command.form!, { count: 2, day: "fri" }, helpers),
|
|
/Day has an invalid value/,
|
|
);
|
|
assert.throws(
|
|
() => validateCommandSpec(
|
|
{ id: "bad", title: "Bad", icon: "definitely-not-a-host-icon" } as never,
|
|
() => ({ kind: "icons", name: "ignored", path: "ignored" }),
|
|
helpers,
|
|
),
|
|
/Invalid plugin command icon/,
|
|
);
|
|
|
|
console.error("Plugin SDK command validation seam passed.");
|