- Rename all user-facing and technical identifiers from OpenPets/Pet to FamiliarOS/Familiar. - Rename packages from @open-pets/* to @familiaros/*; rename install-pet/pet-format packages. - Rename plugin IDs and directories from openpets.* to familiaros.*. - Rename IPC namespace from openpets:* to familiaros:* and state filenames from openpets-* to familiaros-* with legacy migration. - Rename source files (pet-window, built-in-pet, default-pet-controller, etc.) to familiar equivalents. - Update locales (en, es-419, ja, ko, pt-BR, zh-Hans, zh-Hant) and tray/pet context menu strings. - Add Familiar naming feature: preference, settings input, tray menu display. - Update assets and packaging config; all desktop tests pass.
78 lines
4.5 KiB
TypeScript
78 lines
4.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
|
|
import { getEffectivePluginConfig, getPluginDefaultConfig, resolvePluginNumericConfig, resolvePluginStringConfig, validatePluginConfigReplacement } from "../src/plugin-config.js";
|
|
import type { FamiliarOSPluginManifest } from "../src/plugin-manifest.js";
|
|
|
|
const base = manifest();
|
|
|
|
assert.deepEqual(getPluginDefaultConfig(base), { enabled: true, intervalMinutes: 10, message: "Stretch", mood: "calm" });
|
|
|
|
assertInvalidReplacement({ message: null }, "invalid_config_value");
|
|
assertInvalidReplacement({ message: ["x"] }, "invalid_config_value");
|
|
assertInvalidReplacement({ message: { text: "x" } }, "invalid_config_value");
|
|
assertInvalidReplacement({ missing: true }, "unknown_config_key");
|
|
assertInvalidReplacement({ intervalMinutes: Number.POSITIVE_INFINITY }, "invalid_config_value");
|
|
assertInvalidReplacement({ mood: "unknown" }, "invalid_select_value");
|
|
assertInvalidReplacement(new Date(), "invalid_config");
|
|
assertInvalidReplacement(new Map(), "invalid_config");
|
|
assertInvalidReplacement([], "invalid_config");
|
|
const soundManifest = manifest({ configSchema: { sound: { type: "sound" } } });
|
|
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: "alert" }).ok, true);
|
|
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: { kind: "user-sound", id: "a".repeat(32), name: "Ding" } }).ok, true);
|
|
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: { kind: "user-sound", id: "../secret" } }).ok, false);
|
|
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: { kind: "user-sound", id: "not-a-hash" } }).ok, false);
|
|
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: "/tmp/ding.wav" }).ok, false);
|
|
assert.deepEqual(validatePluginConfigReplacement(base, { message: "Move", details: "Now", intervalMinutes: 12, enabled: false, mood: "active" }), {
|
|
ok: true,
|
|
config: { details: "Now", enabled: false, intervalMinutes: 12, message: "Move", mood: "active" },
|
|
errors: [],
|
|
});
|
|
|
|
assert.deepEqual(getEffectivePluginConfig(base, { stale: { old: true }, message: "Hydrate" }), {
|
|
ok: true,
|
|
config: { enabled: true, intervalMinutes: 10, message: "Hydrate", mood: "calm" },
|
|
errors: [],
|
|
});
|
|
const invalidPersisted = getEffectivePluginConfig(base, { intervalMinutes: "10" });
|
|
assert.equal(invalidPersisted.ok, false);
|
|
assert.equal(invalidPersisted.errors[0]?.code, "invalid_config_value");
|
|
|
|
assert.equal(resolvePluginNumericConfig(base, {}, "intervalMinutes", { min: 5 }), 10);
|
|
assert.equal(resolvePluginNumericConfig(base, { intervalMinutes: 7, stale: true }, "intervalMinutes", { min: 5 }), 7);
|
|
assert.throws(() => resolvePluginNumericConfig(base, { intervalMinutes: 4 }, "intervalMinutes", { min: 5 }), /at least 5/);
|
|
assert.throws(() => resolvePluginNumericConfig(manifest({ configSchema: { intervalMinutes: { type: "number" } } }), {}, "intervalMinutes", { min: 5 }), /must resolve to an integer/);
|
|
assert.throws(() => resolvePluginNumericConfig(base, { intervalMinutes: 6.5 }, "intervalMinutes", { min: 5 }), /must resolve to an integer/);
|
|
|
|
assert.equal(resolvePluginStringConfig(base, {}, "message", "text"), "Stretch");
|
|
assert.equal(resolvePluginStringConfig(base, { message: "Move" }, "message", "text"), "Move");
|
|
assert.equal(resolvePluginStringConfig(base, {}, "mood", "select"), "calm");
|
|
assert.throws(() => resolvePluginStringConfig(base, { message: 1 }, "message", "text"), /invalid/);
|
|
assert.throws(() => resolvePluginStringConfig(manifest({ configSchema: { message: { type: "text" } } }), {}, "message", "text"), /resolve to a value/);
|
|
assert.throws(() => resolvePluginStringConfig(base, {}, "intervalMinutes", "text"), /text config field/);
|
|
|
|
console.error("Plugin config validation passed.");
|
|
|
|
function assertInvalidReplacement(config: unknown, code: string): void {
|
|
const result = validatePluginConfigReplacement(base, config);
|
|
assert.equal(result.ok, false);
|
|
assert.ok(result.errors.some((error) => error.code === code), JSON.stringify(result.errors));
|
|
}
|
|
|
|
function manifest(patch: Partial<FamiliarOSPluginManifest> = {}): FamiliarOSPluginManifest {
|
|
return {
|
|
manifestVersion: 1,
|
|
id: "plug",
|
|
name: "Plug",
|
|
version: "1.0.0",
|
|
runtime: "declarative",
|
|
permissions: ["timer"],
|
|
configSchema: patch.configSchema ?? {
|
|
message: { type: "text", default: "Stretch" },
|
|
details: { type: "textarea" },
|
|
intervalMinutes: { type: "number", default: 10 },
|
|
enabled: { type: "boolean", default: true },
|
|
mood: { type: "select", default: "calm", options: [{ label: "Calm", value: "calm" }, { label: "Active", value: "active" }] },
|
|
},
|
|
triggers: [],
|
|
};
|
|
}
|