feat: point core frontend snippets pickers at the plugin (B2)

This commit is contained in:
LukeGus committed 2026-09-23 08:59:06 -05:00
1 parent e07221eaf3
commit 1eac0f54b0
22 files changed
+170 -2968

No files matched your search

+34 -17
View File
@@ -1,7 +1,8 @@
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { dispatchKeybindingAction } from "../../lib/keybinding-dispatch";
import type { Terminal } from "@xterm/xterm";
import type { KeybindingDispatchContext } from "../../lib/keybinding-dispatch";
import * as actionRegistry from "../../shell/action-registry";
function makeContext(
overrides: Partial<KeybindingDispatchContext> = {},
@@ -25,7 +26,6 @@ function makeContext(
webSocketRef: { current: ws as unknown as WebSocket },
writeTextToClipboard: vi.fn().mockResolvedValue(true),
readTextFromClipboard: vi.fn().mockResolvedValue(""),
getSnippetById: vi.fn().mockResolvedValue(undefined),
sentData,
...overrides,
};
@@ -33,6 +33,10 @@ function makeContext(
}
describe("dispatchKeybindingAction", () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it("copy writes the selection to clipboard and clears it", () => {
const ctx = makeContext();
(ctx.terminal.getSelection as ReturnType<typeof vi.fn>).mockReturnValue(
@@ -84,45 +88,58 @@ describe("dispatchKeybindingAction", () => {
expect(ctx.sentData).toEqual(["ls -la\r"]);
});
it("runSnippet resolves the snippet and sends its content with a trailing return", async () => {
const ctx = makeContext({
getSnippetById: vi.fn().mockResolvedValue({ content: "uptime" }),
it("runSnippet resolves the snippet through the snippets.resolveForTerminal action and sends its content with a trailing return", async () => {
vi.spyOn(actionRegistry, "invokeAction").mockResolvedValue({
needsInputs: false,
content: "uptime",
});
const ctx = makeContext();
dispatchKeybindingAction({ type: "runSnippet", snippetId: "1" }, ctx);
await Promise.resolve();
await Promise.resolve();
expect(actionRegistry.invokeAction).toHaveBeenCalledWith(
"snippets.resolveForTerminal",
1,
null,
);
expect(ctx.sentData).toEqual(["uptime\r"]);
});
it("runSnippet does nothing when the snippet no longer exists", async () => {
const ctx = makeContext({
getSnippetById: vi.fn().mockResolvedValue(undefined),
});
vi.spyOn(actionRegistry, "invokeAction").mockResolvedValue(undefined);
const ctx = makeContext();
dispatchKeybindingAction({ type: "runSnippet", snippetId: "1" }, ctx);
await Promise.resolve();
await Promise.resolve();
expect(ctx.sentData).toEqual([]);
});
it("runSnippet silently resolves host variables from hostContext", async () => {
it("runSnippet passes hostContext through to the resolve action", async () => {
vi.spyOn(actionRegistry, "invokeAction").mockResolvedValue({
needsInputs: false,
content: "ping 10.0.0.5 -p 22",
});
const ctx = makeContext({
getSnippetById: vi
.fn()
.mockResolvedValue({ content: "ping $HOST -p $PORT" }),
hostContext: { ip: "10.0.0.5", username: "root", port: 22 },
});
dispatchKeybindingAction({ type: "runSnippet", snippetId: "1" }, ctx);
await Promise.resolve();
await Promise.resolve();
expect(actionRegistry.invokeAction).toHaveBeenCalledWith(
"snippets.resolveForTerminal",
1,
{ ip: "10.0.0.5", username: "root", port: 22 },
);
expect(ctx.sentData).toEqual(["ping 10.0.0.5 -p 22\r"]);
});
it("runSnippet defers to onSnippetNeedsInputs instead of sending when $INPUT_n is present", async () => {
const onSnippetNeedsInputs = vi.fn();
const ctx = makeContext({
getSnippetById: vi.fn().mockResolvedValue({ content: "echo $INPUT_1" }),
onSnippetNeedsInputs,
it("runSnippet defers to onSnippetNeedsInputs instead of sending when the action reports needsInputs", async () => {
vi.spyOn(actionRegistry, "invokeAction").mockResolvedValue({
needsInputs: true,
content: "echo $INPUT_1",
});
const onSnippetNeedsInputs = vi.fn();
const ctx = makeContext({ onSnippetNeedsInputs });
dispatchKeybindingAction({ type: "runSnippet", snippetId: "1" }, ctx);
await Promise.resolve();
await Promise.resolve();
@@ -1,79 +0,0 @@
import { describe, it, expect } from "vitest";
import {
extractSnippetInputs,
hasSnippetInputs,
resolveSnippetContent,
} from "../../lib/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");
});
});