feat: move terminal settings, look and ssh tools into ssh-terminal plugin

This commit is contained in:
LukeGus committed 2026-09-29 02:38:37 -05:00
1 parent ffa872ebd3
commit d9bba10612
242 files changed
+25071 -7207

No files matched your search

+50 -129
View File
@@ -65,8 +65,7 @@ describe("omitOwnerSshAuthFromSharedEdit", () => {
expect(sharedEdit.name).toBe(payload.name);
expect(sharedEdit.ip).toBe("10.0.0.42");
expect(sharedEdit.notes).toBe("editable");
expect(sharedEdit.terminalConfig?.sudoPassword).toBeUndefined();
expect(sharedEdit.terminalConfig?.agentSocketPath).toBeUndefined();
expect(sharedEdit.sshOptions?.agentSocketPath).toBeUndefined();
for (const field of [
"authType",
"credentialId",
@@ -169,10 +168,7 @@ describe("buildHostEditorPayload auth field isolation", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload.sshOptions as unknown as Record<string, unknown> | null;
expect(tc?.agentSocketPath).toBe("/run/user/1000/gnupg/S.gpg-agent.ssh");
expect(payload.password).toBeNull();
@@ -187,10 +183,7 @@ describe("buildHostEditorPayload auth field isolation", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload.sshOptions as unknown as Record<string, unknown> | null;
expect(tc?.agentSocketPath).toBeNull();
});
@@ -204,15 +197,12 @@ describe("buildHostEditorPayload auth field isolation", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload.sshOptions as unknown as Record<string, unknown> | null;
expect(tc?.agentSocketPath).toBeNull();
});
it("preserves agentIdentity in terminalConfig when authType is agent", () => {
it("preserves agentIdentity in sshOptions when authType is agent", () => {
const form = {
...createHostEditorForm(null),
authType: "agent" as const,
@@ -220,10 +210,7 @@ describe("buildHostEditorPayload auth field isolation", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload.sshOptions as unknown as Record<string, unknown> | null;
expect(tc?.agentIdentity).toBe(
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA test-key",
@@ -239,15 +226,12 @@ describe("buildHostEditorPayload auth field isolation", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload.sshOptions as unknown as Record<string, unknown> | null;
expect(tc?.agentIdentity).toBeNull();
});
it("keeps agentIdentity in terminalConfig for shared edits (not owner-private)", () => {
it("keeps agentIdentity in sshOptions for shared edits (not owner-private)", () => {
const form = {
...createHostEditorForm(null),
authType: "agent" as const,
@@ -257,39 +241,10 @@ describe("buildHostEditorPayload auth field isolation", () => {
const payload = buildHostEditorPayload(form, sshOnly);
const sharedEdit = omitOwnerSshAuthFromSharedEdit(payload);
expect(sharedEdit.terminalConfig?.agentIdentity).toBe(
expect(sharedEdit.sshOptions?.agentIdentity).toBe(
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA test-key",
);
});
it("preserves sudo password autofill settings", () => {
const form = {
...createHostEditorForm(null),
sudoPasswordAutoFill: true,
sudoPassword: "sudo-secret",
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
expect(tc?.sudoPasswordAutoFill).toBe(true);
expect(tc?.sudoPassword).toBe("sudo-secret");
});
it("defaults password prompt autofill on and saves it when turned off", () => {
expect(createHostEditorForm(null).passwordPromptAutoFill).toBe(true);
const form = {
...createHostEditorForm(null),
passwordPromptAutoFill: false,
};
const payload = buildHostEditorPayload(form, sshOnly);
expect(payload.terminalConfig?.passwordPromptAutoFill).toBe(false);
});
});
describe("sudo password persistence indicator", () => {
@@ -305,10 +260,7 @@ describe("sudo password persistence indicator", () => {
const form = { ...createHostEditorForm(host) };
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload as unknown as Record<string, unknown>;
expect(tc?.sudoPassword).toBeUndefined();
expect(JSON.parse(JSON.stringify(tc))).not.toHaveProperty("sudoPassword");
@@ -322,15 +274,12 @@ describe("sudo password persistence indicator", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload as unknown as Record<string, unknown>;
expect(tc?.sudoPassword).toBe("new-sudo-pass");
});
it("sends null to explicitly clear a saved sudo password", () => {
it("sends an empty value to explicitly clear a saved sudo password", () => {
const host = { hasSudoPassword: true } as Host;
const form = {
...createHostEditorForm(host),
@@ -338,12 +287,9 @@ describe("sudo password persistence indicator", () => {
};
const payload = buildHostEditorPayload(form, sshOnly);
const tc = payload.terminalConfig as unknown as Record<
string,
unknown
> | null;
const tc = payload as unknown as Record<string, unknown>;
expect(tc?.sudoPassword).toBeNull();
expect(tc?.sudoPassword).toBe("");
});
});
@@ -434,43 +380,6 @@ describe("RDP/VNC/Telnet password persistence indicator", () => {
});
});
describe("user connection defaults", () => {
const defaults = {
terminal: { fontSize: 18, cursorBlink: false },
};
it("shows inherited values without persisting them as host overrides", () => {
const host = {
enableSsh: true,
terminalConfig: { autoTmux: true },
} as unknown as Host;
const form = createHostEditorForm(host, undefined, defaults);
expect(form).toMatchObject({
fontSize: 18,
cursorBlink: false,
inheritTerminalAppearance: true,
});
const payload = buildHostEditorPayload(form, sshOnly);
expect(payload.terminalConfig).toMatchObject({ autoTmux: true });
expect(payload.terminalConfig).not.toHaveProperty("fontSize");
});
it("keeps explicit host overrides above user defaults", () => {
const host = {
enableSsh: true,
terminalConfig: { fontSize: 12 },
} as unknown as Host;
const form = createHostEditorForm(host, undefined, defaults);
expect(form).toMatchObject({
fontSize: 12,
inheritTerminalAppearance: false,
});
});
});
describe("createHostEditorForm credentialId", () => {
it("coerces a numeric credentialId to a string so credential lookups match", () => {
const form = createHostEditorForm({
@@ -489,20 +398,6 @@ describe("createHostEditorForm credentialId", () => {
});
});
describe("createHostEditorForm auto-tmux", () => {
it("inherits the admin default for a new host but keeps an existing host's own choice", () => {
expect(createHostEditorForm(null, { autoTmux: true }).autoTmux).toBe(true);
expect(createHostEditorForm(null, {}).autoTmux).toBe(false);
const host = {
id: "1",
name: "box",
terminalConfig: { autoTmux: false },
} as unknown as Host;
expect(createHostEditorForm(host, { autoTmux: true }).autoTmux).toBe(false);
});
});
// Support#1240: RDP/VNC/Telnet can now originate from the desktop, so the
// control has to appear for hosts that enable only those protocols -- it used
// to be gated on SSH alone.
@@ -532,18 +427,44 @@ describe("connectionOriginAppliesTo", () => {
});
});
describe("macOS Option character defaults", () => {
it("leaves Option available for keyboard-layout characters by default", () => {
const form = createHostEditorForm(null);
expect(form.macOptionIsMeta).toBe(false);
expect(
buildHostEditorPayload(form, sshOnly).terminalConfig?.macOptionIsMeta,
).toBe(false);
describe("terminal fields", () => {
it("sends only the startup snippet in terminalConfig, and the SSH options on their own", () => {
const form = {
...createHostEditorForm(null),
startupSnippetId: 4,
keepaliveInterval: 30,
agentForwarding: true,
environmentVariables: [{ key: "LANG", value: "C" }],
};
const payload = buildHostEditorPayload(form, sshOnly);
expect(payload.terminalConfig).toEqual({ startupSnippetId: 4 });
expect(payload.sshOptions).toMatchObject({
keepaliveInterval: 30,
keepaliveCountMax: 5,
allowLegacyAlgorithms: true,
agentForwarding: true,
environmentVariables: [{ key: "LANG", value: "C" }],
});
});
it("preserves an explicitly saved Meta preference", () => {
const host = { terminalConfig: { macOptionIsMeta: true } } as Host;
expect(createHostEditorForm(host).macOptionIsMeta).toBe(true);
it("reads a host's SSH options back into the form", () => {
const host = {
sshOptions: { keepaliveInterval: 15, allowLegacyAlgorithms: false },
terminalConfig: { startupSnippetId: 2 },
} as unknown as Host;
const form = createHostEditorForm(host);
expect(form.keepaliveInterval).toBe(15);
expect(form.allowLegacyAlgorithms).toBe(false);
expect(form.startupSnippetId).toBe(2);
});
it("leaves the terminal fields out with SSH off", () => {
const payload = buildHostEditorPayload(createHostEditorForm(null), rdpOnly);
expect(payload).not.toHaveProperty("terminalConfig");
expect(payload).not.toHaveProperty("sshOptions");
expect(payload).not.toHaveProperty("sudoPassword");
});
});
@@ -1,60 +0,0 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { HostTerminalSettings } from "@/sidebar/HostTerminalSettings";
import { createHostEditorForm } from "@/sidebar/HostEditorData";
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
vi.mock("@/api/open-tabs-api", () => ({
getUserPreferences: vi.fn().mockResolvedValue({ customThemes: null }),
saveUserPreferences: vi.fn(),
parseCustomThemes: () => [],
}));
vi.mock("@/components/theme-provider", () => ({
useTheme: () => ({ theme: "dark" }),
}));
globalThis.ResizeObserver ??= class {
observe() {}
unobserve() {}
disconnect() {}
} as unknown as typeof ResizeObserver;
afterEach(cleanup);
function renderSettings(setField = vi.fn()) {
const form = createHostEditorForm(undefined);
render(
<HostTerminalSettings
form={form}
setField={setField}
snippets={[{ id: 7, name: "deploy" }]}
/>,
);
return setField;
}
describe("HostTerminalSettings", () => {
it("renders the appearance and behavior cards", () => {
renderSettings();
expect(screen.getByText("hosts.terminalAppearance")).toBeTruthy();
expect(screen.getByText("hosts.behaviorAndAdvanced")).toBeTruthy();
});
it("leaves the keepalive fields to the SSH tab", () => {
renderSettings();
expect(screen.queryByText("hosts.keepaliveIntervalLabel")).toBeNull();
});
it("sets the startup snippet from the offered snippets", () => {
const setField = renderSettings();
const select = screen
.getByRole("option", { name: "deploy" })
.closest("select")!;
fireEvent.change(select, { target: { value: "7" } });
expect(setField).toHaveBeenCalledWith("startupSnippetId", 7);
});
});
+5 -6
View File
@@ -52,13 +52,12 @@ describe("RAIL_ITEMS", () => {
// "port-forwarding" to the tunnels plugin's, "serial" to the serial
// plugin's, "collab" to the session-sharing plugin's, and "session-logs"
// to the session-recording plugin's, "termix-id" to the termix-identity
// plugin's.
// plugin's, "ssh-tools" to the ssh-terminal plugin's.
expect(RAIL_ITEMS.map((item) => item.id)).toEqual([
"hosts",
"credentials",
"connections",
"quick-connect",
"ssh-tools",
"macros",
"sync",
]);
@@ -70,10 +69,10 @@ describe("RAIL_ITEMS", () => {
);
});
it("marks exactly the three mobile primary slots", () => {
it("marks core's mobile primary slots", () => {
expect(
RAIL_ITEMS.filter((item) => item.mobilePrimary).map((item) => item.id),
).toEqual(["hosts", "quick-connect", "ssh-tools"]);
).toEqual(["hosts", "quick-connect"]);
});
it("marks the panels that can open as a tab", () => {
@@ -81,7 +80,7 @@ describe("RAIL_ITEMS", () => {
[...RAIL_ITEMS, ...RAIL_UTILITY_ITEMS]
.filter((item) => item.promotable)
.map((item) => item.id),
).toEqual(["ssh-tools", "macros"]);
).toEqual(["macros"]);
});
it("derives promotableIds from the promotable flag", () => {
@@ -118,7 +117,7 @@ describe("RAIL_ITEMS", () => {
});
it("only offers reference panels in the right dock", () => {
expect(rightDockableIds()).toEqual(["connections", "ssh-tools", "macros"]);
expect(rightDockableIds()).toEqual(["connections", "macros"]);
});
});