feat: move snippets, keybinding actions and macros out of core into plugins

This commit is contained in:
LukeGus committed 2026-09-29 11:39:14 -05:00
1 parent d9bba10612
commit e0c30a4e6f
262 files changed
+9091 -10744

No files matched your search

@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { screen } from "@testing-library/react";
import { act, fireEvent, screen } from "@testing-library/react";
import { runKeybindingAction } from "@termix/plugin-sdk/frontend";
import {
renderWithApp,
type RenderedPluginApp,
@@ -41,11 +42,24 @@ describe(`${manifest.id} activate`, () => {
expect(railItem?.permission).toBe("snippets.view");
});
it("registers the snippets.resolveForTerminal action", async () => {
it("registers what other surfaces reach it through", async () => {
rendered = await renderWithApp(plugin, { manifest, locales });
expect(rendered.registered.actions()).toContain(
"snippets.resolveForTerminal",
expect(rendered.registered.actions()).toEqual(
expect.arrayContaining([
"snippets.startupCommand",
"snippets.list",
"snippets.extractInputs",
]),
);
expect(rendered.registered.paletteGroups()).toEqual(["snippets"]);
expect(rendered.registered.keybindingActions()).toEqual(["runSnippet"]);
expect(rendered.registered.hostEditorSections()).toEqual(["snippets"]);
expect(rendered.registered.slot("host-metrics.toolbar")).toEqual([
"snippets.quickActions",
]);
expect(rendered.registered.slot("shell.overlay")).toEqual([
"snippets.overlay",
]);
});
it("removes everything it registered on deactivate", async () => {
@@ -54,6 +68,143 @@ describe(`${manifest.id} activate`, () => {
expect(app.registered.panels()).toEqual([]);
expect(app.registered.railItems()).toEqual([]);
expect(app.registered.actions()).toEqual([]);
expect(app.registered.paletteGroups()).toEqual([]);
expect(app.registered.keybindingActions()).toEqual([]);
});
});
const deploy = {
id: 3,
name: "Deploy",
content: "deploy $HOST",
description: null,
isNote: false,
};
describe("snippets in the command palette", () => {
it("lists snippets that run in the target tab", async () => {
const api = { get: vi.fn(async () => ({ data: [deploy] })) };
rendered = await renderWithApp(plugin, {
manifest,
locales,
api: api as never,
permissions: ["snippets.view"],
});
const items = await rendered.loadPaletteGroup("snippets");
expect(items).toEqual([
expect.objectContaining({
id: "3",
title: "Deploy",
description: "deploy $HOST",
needsTarget: true,
}),
]);
});
it("offers nothing without the view permission", async () => {
rendered = await renderWithApp(plugin, {
manifest,
locales,
permissions: [],
});
expect(await rendered.loadPaletteGroup("snippets")).toEqual([]);
});
});
describe("the runSnippet keybinding", () => {
it("types the resolved snippet into the session it was pressed in", async () => {
const api = { get: vi.fn(async () => ({ data: deploy })) };
rendered = await renderWithApp(plugin, {
manifest,
locales,
api: api as never,
});
const send = vi.fn();
const handled = runKeybindingAction(
{ type: "runSnippet", snippetId: "3", appendEnter: true },
{ host: { ip: "10.0.0.9" }, send },
);
expect(handled).toBe(true);
await vi.waitFor(() =>
expect(send).toHaveBeenCalledWith("deploy 10.0.0.9\r"),
);
expect(api.get).toHaveBeenCalledWith("/3");
});
});
describe("the startup command", () => {
it("resolves the host's startup snippet, or nothing without one", async () => {
const api = { get: vi.fn(async () => ({ data: deploy })) };
rendered = await renderWithApp(plugin, {
manifest,
locales,
api: api as never,
});
const host = {
id: "1",
name: "web",
ip: "10.0.0.2",
port: 22,
pluginSettings: { snippets: { startupSnippetId: 3 } },
};
expect(
await rendered.app.invokeAction("snippets.startupCommand", host),
).toBe("deploy 10.0.0.2");
expect(
await rendered.app.invokeAction("snippets.startupCommand", {
...host,
pluginSettings: {},
}),
).toBeNull();
});
});
describe("the host editor's Snippets tab", () => {
it("writes the startup snippet and quick actions into this plugin's settings", async () => {
const api = { get: vi.fn(async () => ({ data: [deploy] })) };
rendered = await renderWithApp(plugin, {
manifest,
locales,
api: api as never,
});
let form: Record<string, unknown> = {
pluginSettings: { other: { keep: 1 } },
};
const updateForm = (
patch: (current: Record<string, unknown>) => Record<string, unknown>,
) => {
form = patch(form);
};
rendered.renderHostEditorSection("snippets", { form, updateForm });
const startup = await screen.findByRole("combobox", {
name: "Startup Snippet",
});
await screen.findAllByRole("option", { name: "Deploy" });
act(() => {
fireEvent.change(startup, { target: { value: "3" } });
});
expect(form.pluginSettings).toEqual({
other: { keep: 1 },
snippets: { startupSnippetId: 3 },
});
});
});
describe("the Host Metrics quick action buttons", () => {
it("draws a button per quick action and nothing without any", async () => {
rendered = await renderWithApp(plugin, {
manifest,
locales,
permissions: ["snippets.view"],
});
const host = {
id: 4,
pluginSettings: {
snippets: { quickActions: [{ name: "Restart", snippetId: 3 }] },
},
};
rendered.renderSlot("host-metrics.toolbar", { hostId: 4, host });
expect(await screen.findByRole("button", { name: "Restart" })).toBeTruthy();
});
});
@@ -31,22 +31,23 @@ afterEach(async () => {
rendered = null;
});
describe("snippet.list", () => {
it("lists the user's own snippets", async () => {
describe("snippet pickers while an admin edits another user's host", () => {
it("list the user's own snippets", async () => {
const api = fakeApi([{ id: 1, name: "a", content: "ls", isNote: true }]);
rendered = await renderWithApp(plugin, { manifest, locales, api });
const rows = await rendered.app.invokeAction("snippet.list");
rendered.renderHostEditorSection("snippets", { form: {} });
await screen.findAllByRole("option", { name: "a" });
expect(api.get).toHaveBeenCalledWith("/");
expect(rows).toEqual([
{ id: 1, name: "a", content: "ls", folder: null, isNote: true },
]);
});
it("lists another user's snippets for an admin", async () => {
it("list the target user's snippets", async () => {
const api = fakeApi([]);
rendered = await renderWithApp(plugin, { manifest, locales, api });
await rendered.app.invokeAction("snippet.list", { targetUserId: "u2" });
expect(api.get).toHaveBeenCalledWith("/", TARGET);
rendered.renderHostEditorSection("snippets", {
form: {},
adminTargetUserId: "u2",
});
await waitFor(() => expect(api.get).toHaveBeenCalledWith("/", TARGET));
});
});
@@ -1,79 +0,0 @@
import { describe, it, expect } from "vitest";
import {
extractSnippetInputs,
hasSnippetInputs,
resolveSnippetContent,
} from "../../src/frontend/snippet-variables";
describe("extractSnippetInputs", () => {
it("extracts a single unlabeled input", () => {
expect(extractSnippetInputs("ping $INPUT_1")).toEqual([
{ key: "INPUT_1", label: "Input 1" },
]);
});
it("extracts a labeled input using brace syntax", () => {
expect(
extractSnippetInputs("nc -zv $HOST ${INPUT_1:Port to check}"),
).toEqual([{ key: "INPUT_1", label: "Port to check" }]);
});
it("plain (non-braced) references have no label", () => {
expect(extractSnippetInputs("echo $INPUT_2")).toEqual([
{ key: "INPUT_2", label: "Input 2" },
]);
});
it("de-duplicates repeated references and preserves first-seen order", () => {
expect(
extractSnippetInputs("$INPUT_2 then $INPUT_1 then $INPUT_2 again"),
).toEqual([
{ key: "INPUT_2", label: "Input 2" },
{ key: "INPUT_1", label: "Input 1" },
]);
});
it("returns an empty array when there are no placeholders", () => {
expect(extractSnippetInputs("uptime")).toEqual([]);
});
});
describe("hasSnippetInputs", () => {
it("returns true when a snippet has an input placeholder", () => {
expect(hasSnippetInputs("echo $INPUT_1")).toBe(true);
});
it("returns false when a snippet has no input placeholder", () => {
expect(hasSnippetInputs("echo $HOST")).toBe(false);
});
});
describe("resolveSnippetContent", () => {
const host = { ip: "10.0.0.5", username: "root", port: 22, name: "web-01" };
it("substitutes host variables", () => {
expect(
resolveSnippetContent("ssh $USER@$HOST -p $PORT # $NAME", host),
).toBe("ssh root@10.0.0.5 -p 22 # web-01");
});
it("supports brace syntax for host variables", () => {
expect(resolveSnippetContent("ping ${HOST}", host)).toBe("ping 10.0.0.5");
});
it("substitutes input placeholders", () => {
expect(
resolveSnippetContent("nc -zv $HOST ${INPUT_1:Port}", host, {
INPUT_1: "8080",
}),
).toBe("nc -zv 10.0.0.5 8080");
});
it("leaves host variables literal when no host context is given", () => {
expect(resolveSnippetContent("ping $HOST", null)).toBe("ping $HOST");
});
it("leaves input placeholders literal when no value was supplied", () => {
expect(resolveSnippetContent("echo $INPUT_1", null)).toBe("echo $INPUT_1");
});
});