[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:
194
components/terminal/TerminalToolbar.test.ts
Normal file
194
components/terminal/TerminalToolbar.test.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "../../application/i18n/I18nProvider.tsx";
|
||||
import type { Host } from "../../types.ts";
|
||||
import { TerminalToolbar } from "./TerminalToolbar.tsx";
|
||||
|
||||
const toolbarSource = readFileSync(new URL("./TerminalToolbar.tsx", import.meta.url), "utf8");
|
||||
|
||||
const sshHost: Host = {
|
||||
id: "host-1",
|
||||
label: "Host",
|
||||
hostname: "example.com",
|
||||
username: "root",
|
||||
tags: [],
|
||||
os: "linux",
|
||||
protocol: "ssh",
|
||||
};
|
||||
|
||||
const serialHost: Host = {
|
||||
...sshHost,
|
||||
id: "serial-1",
|
||||
label: "Serial",
|
||||
hostname: "/dev/tty.usbserial",
|
||||
protocol: "serial",
|
||||
};
|
||||
|
||||
const pluginHost: Host = {
|
||||
...sshHost,
|
||||
id: "plugin-1",
|
||||
label: "Plugin",
|
||||
hostname: "com.example.transport.connection",
|
||||
protocol: "plugin:com.example.transport.connection",
|
||||
};
|
||||
|
||||
const renderToolbar = (
|
||||
host: Host,
|
||||
status: "connecting" | "connected" | "disconnected" = "connected",
|
||||
props: Partial<React.ComponentProps<typeof TerminalToolbar>> = {},
|
||||
) =>
|
||||
renderToStaticMarkup(
|
||||
React.createElement(
|
||||
I18nProvider,
|
||||
{ locale: "en" },
|
||||
React.createElement(TerminalToolbar, {
|
||||
sessionId: 'session-1',
|
||||
status,
|
||||
host,
|
||||
onOpenSFTP: () => {},
|
||||
onOpenScripts: () => {},
|
||||
onOpenTheme: () => {},
|
||||
...props,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
test("keeps SFTP visible before the terminal overflow menu for SSH sessions", () => {
|
||||
const markup = renderToolbar(sshHost);
|
||||
|
||||
const sftpIndex = markup.indexOf('aria-label="Open SFTP"');
|
||||
const moreIndex = markup.indexOf('aria-label="More actions"');
|
||||
|
||||
assert.notEqual(sftpIndex, -1);
|
||||
assert.notEqual(moreIndex, -1);
|
||||
assert.ok(sftpIndex < moreIndex);
|
||||
});
|
||||
|
||||
test("keeps Scripts visible before the terminal overflow menu", () => {
|
||||
const markup = renderToolbar(sshHost);
|
||||
|
||||
const scriptsIndex = markup.indexOf('aria-label="Scripts"');
|
||||
const moreIndex = markup.indexOf('aria-label="More actions"');
|
||||
|
||||
assert.notEqual(scriptsIndex, -1);
|
||||
assert.notEqual(moreIndex, -1);
|
||||
assert.ok(scriptsIndex < moreIndex);
|
||||
assert.equal(markup.match(/Scripts/g)?.length, 1);
|
||||
assert.match(markup, /type="button"[^>]*aria-label="Scripts"/);
|
||||
});
|
||||
|
||||
test("shows manual session log button when requested", () => {
|
||||
const markup = renderToolbar(sshHost, "connected", {
|
||||
showLogButton: true,
|
||||
onToggleSessionLog: () => {},
|
||||
});
|
||||
|
||||
const logIndex = markup.indexOf('aria-label="Start session log"');
|
||||
const scriptsIndex = markup.indexOf('aria-label="Scripts"');
|
||||
|
||||
assert.notEqual(logIndex, -1);
|
||||
assert.notEqual(scriptsIndex, -1);
|
||||
assert.ok(logIndex < scriptsIndex);
|
||||
});
|
||||
|
||||
test("marks manual session log button active while logging", () => {
|
||||
const markup = renderToolbar(sshHost, "connected", {
|
||||
showLogButton: true,
|
||||
onToggleSessionLog: () => {},
|
||||
isSessionLogging: true,
|
||||
});
|
||||
|
||||
assert.match(
|
||||
markup,
|
||||
/aria-label="Stop session log"[^>]*aria-pressed="true"[^>]*style="background-color:var\(--terminal-toolbar-btn-active\)"/,
|
||||
);
|
||||
});
|
||||
|
||||
test("hides SFTP for local terminal sessions", () => {
|
||||
const markup = renderToolbar({
|
||||
...sshHost,
|
||||
id: "local-1",
|
||||
protocol: "local",
|
||||
});
|
||||
|
||||
assert.equal(markup.includes('aria-label="Open SFTP"'), false);
|
||||
});
|
||||
|
||||
test("hides SSH history for plugin terminal sessions", () => {
|
||||
const markup = renderToolbar(pluginHost, "connected", {
|
||||
onOpenHistory: () => {},
|
||||
});
|
||||
|
||||
assert.equal(markup.includes('aria-label="Command history"'), false);
|
||||
});
|
||||
|
||||
test("shows YMODEM send only for connected serial sessions", () => {
|
||||
const connectedSerial = renderToolbar(serialHost, "connected", {
|
||||
onSendYmodem: () => {},
|
||||
onReceiveYmodem: () => {},
|
||||
});
|
||||
const disconnectedSerial = renderToolbar(serialHost, "disconnected", {
|
||||
onSendYmodem: () => {},
|
||||
onReceiveYmodem: () => {},
|
||||
});
|
||||
const ssh = renderToolbar(sshHost, "connected", {
|
||||
onSendYmodem: () => {},
|
||||
onReceiveYmodem: () => {},
|
||||
});
|
||||
const local = renderToolbar({
|
||||
...sshHost,
|
||||
id: "local-1",
|
||||
protocol: "local",
|
||||
}, "connected", {
|
||||
onSendYmodem: () => {},
|
||||
onReceiveYmodem: () => {},
|
||||
});
|
||||
|
||||
assert.equal(connectedSerial.includes('aria-label="Send with YMODEM"'), true);
|
||||
assert.equal(connectedSerial.includes('aria-label="Receive with YMODEM"'), true);
|
||||
assert.doesNotMatch(connectedSerial, /aria-label="Send with YMODEM"[^>]*disabled/);
|
||||
assert.equal(disconnectedSerial.includes('aria-label="Send with YMODEM - Available after connect"'), true);
|
||||
assert.equal(disconnectedSerial.includes('aria-label="Receive with YMODEM - Available after connect"'), true);
|
||||
assert.match(disconnectedSerial, /aria-label="Send with YMODEM - Available after connect"[^>]*disabled/);
|
||||
assert.match(disconnectedSerial, /aria-label="Receive with YMODEM - Available after connect"[^>]*disabled/);
|
||||
assert.equal(ssh.includes('aria-label="Send with YMODEM"'), false);
|
||||
assert.equal(ssh.includes('aria-label="Receive with YMODEM"'), false);
|
||||
assert.equal(local.includes('aria-label="Send with YMODEM"'), false);
|
||||
assert.equal(local.includes('aria-label="Receive with YMODEM"'), false);
|
||||
});
|
||||
|
||||
test("uses the terminal active button color for pressed toolbar actions", () => {
|
||||
const markup = renderToolbar(sshHost, "connected", {
|
||||
isSearchOpen: true,
|
||||
onToggleSearch: () => {},
|
||||
});
|
||||
|
||||
assert.match(
|
||||
markup,
|
||||
/aria-label="Search terminal"[^>]*style="background-color:var\(--terminal-toolbar-btn-active\)"/,
|
||||
);
|
||||
});
|
||||
|
||||
test("compact scripts popover hosts bulk-delete confirm outside the popover", () => {
|
||||
// Dialog focus leaves PopoverContent; if confirm stays inside ScriptsSidePanel,
|
||||
// the popover closes, isVisible clears pendingDeleteIds, and the prompt dies.
|
||||
assert.match(toolbarSource, /onBulkDeleteRequest=\{setPendingScriptDeleteIds\}/);
|
||||
assert.match(toolbarSource, /<VaultDeleteConfirmDialog/);
|
||||
// Popup vault mutation goes through the prop; the event only clears popover selection.
|
||||
assert.match(toolbarSource, /onDeleteSnippets\?\.\(new Set\(ids\)\)/);
|
||||
assert.match(toolbarSource, /netcatty:snippets:delete/);
|
||||
const scriptsPopoverIdx = toolbarSource.indexOf("open={scriptsPopoverOpen}");
|
||||
const popoverEndIdx = toolbarSource.indexOf("</Popover>", scriptsPopoverIdx);
|
||||
const dialogIdx = toolbarSource.indexOf("<VaultDeleteConfirmDialog", popoverEndIdx);
|
||||
assert.ok(scriptsPopoverIdx >= 0, "scripts popover open binding");
|
||||
assert.ok(popoverEndIdx > scriptsPopoverIdx, "scripts popover closes");
|
||||
assert.ok(dialogIdx > popoverEndIdx, "confirm dialog is a sibling after the popover");
|
||||
assert.equal(
|
||||
toolbarSource.includes("document.querySelector('[data-vault-delete-confirm=\"true\"]')"),
|
||||
false,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user