[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
261
components/terminal/TerminalConnectionDialog.test.ts
Normal file
261
components/terminal/TerminalConnectionDialog.test.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "../../application/i18n/I18nProvider.tsx";
|
||||
import type { Host } from "../../types.ts";
|
||||
import { TerminalConnectionDialog } from "./TerminalConnectionDialog.tsx";
|
||||
|
||||
const host: Host = {
|
||||
id: "host-1",
|
||||
label: "10.2.0.32",
|
||||
hostname: "10.2.0.32",
|
||||
port: 22,
|
||||
username: "root",
|
||||
tags: [],
|
||||
os: "linux",
|
||||
protocol: "ssh",
|
||||
};
|
||||
|
||||
const renderDialog = (
|
||||
props: Partial<React.ComponentProps<typeof TerminalConnectionDialog>> = {},
|
||||
) => renderToStaticMarkup(
|
||||
React.createElement(
|
||||
I18nProvider,
|
||||
{ locale: "en" },
|
||||
React.createElement(TerminalConnectionDialog, {
|
||||
host,
|
||||
status: "connecting",
|
||||
error: null,
|
||||
progressValue: 55,
|
||||
chainProgress: null,
|
||||
needsAuth: false,
|
||||
showLogs: false,
|
||||
_setShowLogs: () => {},
|
||||
keys: [],
|
||||
authProps: {
|
||||
authMethod: "password",
|
||||
setAuthMethod: () => {},
|
||||
authUsername: "root",
|
||||
setAuthUsername: () => {},
|
||||
authPassword: "",
|
||||
setAuthPassword: () => {},
|
||||
authKeyId: null,
|
||||
setAuthKeyId: () => {},
|
||||
authPassphrase: "",
|
||||
setAuthPassphrase: () => {},
|
||||
showAuthPassphrase: false,
|
||||
setShowAuthPassphrase: () => {},
|
||||
showAuthPassword: false,
|
||||
setShowAuthPassword: () => {},
|
||||
authRetryMessage: null,
|
||||
onSubmit: () => {},
|
||||
onCancel: () => {},
|
||||
isValid: true,
|
||||
},
|
||||
progressProps: {
|
||||
timeLeft: 20,
|
||||
isCancelling: false,
|
||||
progressLogs: ["Host key verification required for 10.2.0.32."],
|
||||
onCancelConnect: () => {},
|
||||
onCloseSession: () => {},
|
||||
onRetry: () => {},
|
||||
},
|
||||
...props,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
test("renders host key confirmation inside the connection dialog", () => {
|
||||
const markup = renderDialog({
|
||||
showLogs: true,
|
||||
hostKeyVerification: {
|
||||
hostKeyInfo: {
|
||||
hostname: "10.2.0.32",
|
||||
port: 22,
|
||||
keyType: "ssh-ed25519",
|
||||
fingerprint: "abc123",
|
||||
status: "unknown",
|
||||
},
|
||||
onClose: () => {},
|
||||
onContinue: () => {},
|
||||
onAddAndContinue: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.match(markup, /Confirm this host key/);
|
||||
assert.match(markup, /abc123/);
|
||||
assert.match(markup, /Add and continue/);
|
||||
assert.match(markup, /Host key verification required for 10\.2\.0\.32\./);
|
||||
assert.equal(markup.includes("Timeout in"), false);
|
||||
});
|
||||
|
||||
test("does not show a countdown while waiting for user input", () => {
|
||||
const markup = renderDialog({
|
||||
progressProps: {
|
||||
timeLeft: 20,
|
||||
isAwaitingUserInput: true,
|
||||
isCancelling: false,
|
||||
progressLogs: ["Waiting for passphrase."],
|
||||
onCancelConnect: () => {},
|
||||
onCloseSession: () => {},
|
||||
onRetry: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.match(markup, /Waiting for user input/);
|
||||
assert.equal(markup.includes("Timeout in"), false);
|
||||
});
|
||||
|
||||
test("shows enter reconnect hint when disconnected reconnect is available", () => {
|
||||
const markup = renderDialog({
|
||||
status: "disconnected",
|
||||
error: null,
|
||||
showEnterReconnectHint: true,
|
||||
});
|
||||
|
||||
assert.match(markup, /Press Enter to reconnect/);
|
||||
// Focus sink so Enter still reaches the overlay after body/document blur (#2544).
|
||||
assert.match(markup, /data-terminal-disconnected-dialog="true"/);
|
||||
assert.match(markup, /tabindex="-1"/);
|
||||
});
|
||||
|
||||
test("does not show enter reconnect hint until the caller marks enter reconnect available", () => {
|
||||
const markup = renderDialog({
|
||||
status: "disconnected",
|
||||
error: null,
|
||||
});
|
||||
|
||||
assert.equal(markup.includes("Press Enter to reconnect"), false);
|
||||
assert.equal(markup.includes("data-terminal-disconnected-dialog"), false);
|
||||
});
|
||||
|
||||
test("renders changed host key warning in the same connection dialog", () => {
|
||||
const markup = renderDialog({
|
||||
hostKeyVerification: {
|
||||
hostKeyInfo: {
|
||||
hostname: "10.2.0.32",
|
||||
port: 22,
|
||||
keyType: "ssh-ed25519",
|
||||
fingerprint: "new-fingerprint",
|
||||
knownFingerprint: "old-fingerprint",
|
||||
status: "changed",
|
||||
},
|
||||
onClose: () => {},
|
||||
onContinue: () => {},
|
||||
onAddAndContinue: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.match(markup, /Host key changed/);
|
||||
assert.match(markup, /new-fingerprint/);
|
||||
assert.match(markup, /Saved fingerprint/);
|
||||
assert.match(markup, /old-fingerprint/);
|
||||
assert.match(markup, /Update and continue/);
|
||||
});
|
||||
|
||||
test("keeps the second progress segment parked until the first segment finishes", () => {
|
||||
const markup = renderDialog({ progressValue: 75 });
|
||||
|
||||
assert.match(markup, /style="width:100%"/);
|
||||
assert.match(markup, /style="width:0%"/);
|
||||
});
|
||||
|
||||
test("fills both progress segments for disconnected states", () => {
|
||||
const markup = renderDialog({
|
||||
status: "disconnected",
|
||||
error: "Connection timed out.",
|
||||
progressValue: 5,
|
||||
});
|
||||
|
||||
const fullSegments = markup.match(/style="width:100%"/g) ?? [];
|
||||
assert.equal(fullSegments.length >= 2, true);
|
||||
});
|
||||
|
||||
test("keeps connection log padding inside the scrollable content", () => {
|
||||
const markup = renderDialog({
|
||||
status: "disconnected",
|
||||
error: "Connection timed out.",
|
||||
showLogs: true,
|
||||
progressProps: {
|
||||
timeLeft: 0,
|
||||
isCancelling: false,
|
||||
progressLogs: Array.from({ length: 12 }, (_, index) => `Log line ${index + 1}`),
|
||||
onCancelConnect: () => {},
|
||||
onCloseSession: () => {},
|
||||
onRetry: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.match(markup, /class="[^"]*max-h-44/);
|
||||
assert.doesNotMatch(markup, /class="[^"]*max-h-44[^"]*p-2\.5/);
|
||||
assert.match(markup, /class="[^"]*p-2\.5[^"]*pb-4[^"]*pr-4/);
|
||||
});
|
||||
|
||||
test("shows the ET server port (not the SSH port) for an ET host with a custom etPort", () => {
|
||||
const markup = renderDialog({
|
||||
host: { ...host, etEnabled: true, port: 22, etPort: 9022 },
|
||||
});
|
||||
|
||||
// ET connectivity hinges on the etserver port, so the dialog must show it.
|
||||
assert.match(markup, /10\.2\.0\.32:9022/);
|
||||
assert.equal(markup.includes("10.2.0.32:22"), false);
|
||||
});
|
||||
|
||||
test("defaults the displayed ET port to 2022 when no etPort is set", () => {
|
||||
const markup = renderDialog({
|
||||
host: { ...host, etEnabled: true, port: 22 },
|
||||
});
|
||||
|
||||
assert.match(markup, /10\.2\.0\.32:2022/);
|
||||
assert.equal(markup.includes("10.2.0.32:22"), false);
|
||||
});
|
||||
|
||||
test("labels plugin transports without presenting them as SSH endpoints", () => {
|
||||
const providerId = "com.example.transport.connection";
|
||||
const markup = renderDialog({
|
||||
host: {
|
||||
...host,
|
||||
hostname: providerId,
|
||||
port: 22,
|
||||
protocol: `plugin:${providerId}`,
|
||||
pluginConnection: { providerId, configuration: {} },
|
||||
},
|
||||
});
|
||||
|
||||
assert.match(markup, /Plugin connection/);
|
||||
assert.equal(markup.includes(`${providerId}:22`), false);
|
||||
});
|
||||
|
||||
test("shows restored session copy for disconnected restored placeholders", () => {
|
||||
const markup = renderDialog({
|
||||
status: "disconnected",
|
||||
error: null,
|
||||
restoreState: "restored-disconnected",
|
||||
} as Partial<React.ComponentProps<typeof TerminalConnectionDialog>>);
|
||||
|
||||
assert.match(markup, /Restored session/);
|
||||
assert.match(markup, /This terminal is disconnected/);
|
||||
assert.match(markup, /Reconnect/);
|
||||
});
|
||||
|
||||
test("disconnected observer surfaces do not advertise a reconnect they cannot perform", () => {
|
||||
const markup = renderDialog({
|
||||
status: "disconnected",
|
||||
error: "Observed session ended.",
|
||||
showEnterReconnectHint: false,
|
||||
progressProps: {
|
||||
timeLeft: 0,
|
||||
isCancelling: false,
|
||||
progressLogs: [],
|
||||
onCancelConnect: () => {},
|
||||
onCloseSession: () => {},
|
||||
onRetry: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(markup.includes("Press Enter to reconnect"), false);
|
||||
assert.equal(markup.includes("Start over"), false);
|
||||
assert.match(markup, /Close session/);
|
||||
});
|
||||
Reference in New Issue
Block a user