mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 22:55:24 +00:00
feat: point core frontend snippets pickers at the plugin (B2)
This commit is contained in:
1 parent
e07221eaf3
commit
1eac0f54b0
22 files changed
+170
-2968
No files matched your search
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -51,7 +51,7 @@ describe("ONBOARDING_STEPS", () => {
|
||||
describe("onboarding step body translations", () => {
|
||||
const KEYS: Record<string, string[]> = {
|
||||
welcome: ["hosts", "terminal", "files"],
|
||||
feature: ["files", "tunnels", "snippets"],
|
||||
feature: ["files", "tunnels"],
|
||||
workflow: ["palette", "split", "dock"],
|
||||
security: ["credentials", "twofa", "identity", "sharing"],
|
||||
done: ["settings", "rerun", "docs"],
|
||||
|
||||
@@ -59,7 +59,6 @@ describe("RAIL_ITEMS", () => {
|
||||
"serial",
|
||||
"ssh-tools",
|
||||
"sftp",
|
||||
"snippets",
|
||||
"macros",
|
||||
"history",
|
||||
"session-logs",
|
||||
@@ -74,10 +73,10 @@ describe("RAIL_ITEMS", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("marks exactly the four mobile primary slots", () => {
|
||||
it("marks exactly the three mobile primary slots", () => {
|
||||
expect(
|
||||
RAIL_ITEMS.filter((item) => item.mobilePrimary).map((item) => item.id),
|
||||
).toEqual(["hosts", "quick-connect", "ssh-tools", "snippets"]);
|
||||
).toEqual(["hosts", "quick-connect", "ssh-tools"]);
|
||||
});
|
||||
|
||||
it("marks the panels that can open as a tab", () => {
|
||||
@@ -85,14 +84,7 @@ describe("RAIL_ITEMS", () => {
|
||||
[...RAIL_ITEMS, ...RAIL_UTILITY_ITEMS]
|
||||
.filter((item) => item.promotable)
|
||||
.map((item) => item.id),
|
||||
).toEqual([
|
||||
"termix-id",
|
||||
"ssh-tools",
|
||||
"snippets",
|
||||
"macros",
|
||||
"history",
|
||||
"session-logs",
|
||||
]);
|
||||
).toEqual(["termix-id", "ssh-tools", "macros", "history", "session-logs"]);
|
||||
});
|
||||
|
||||
it("derives promotableIds from the promotable flag", () => {
|
||||
@@ -132,7 +124,6 @@ describe("RAIL_ITEMS", () => {
|
||||
expect(rightDockableIds()).toEqual([
|
||||
"connections",
|
||||
"ssh-tools",
|
||||
"snippets",
|
||||
"macros",
|
||||
"history",
|
||||
"session-logs",
|
||||
|
||||
Reference in new issue
Block a user