[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:
196
components/TerminalPopupPage.test.ts
Normal file
196
components/TerminalPopupPage.test.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import type { Host, TerminalSession } from '../types';
|
||||
import type { TerminalPopupPayload } from '../domain/systemManager/types';
|
||||
import { resolveTerminalPopupHost, resolveTerminalPopupReuseId } from './TerminalPopupPage';
|
||||
|
||||
const source = readFileSync(new URL('./TerminalPopupPage.tsx', import.meta.url), 'utf8');
|
||||
const terminalSource = readFileSync(new URL('./Terminal.tsx', import.meta.url), 'utf8');
|
||||
const terminalLayerSource = readFileSync(new URL('./TerminalLayer.tsx', import.meta.url), 'utf8');
|
||||
|
||||
const vaultHost = (overrides: Partial<Host> = {}): Host => ({
|
||||
id: 'host-1',
|
||||
label: 'Prod',
|
||||
hostname: 'prod.example.com',
|
||||
username: 'deploy',
|
||||
port: 22,
|
||||
protocol: 'ssh',
|
||||
tags: [],
|
||||
os: 'linux',
|
||||
moshEnabled: true,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const sourceSession = (overrides: Partial<TerminalSession> = {}): TerminalSession => ({
|
||||
id: 'session-1',
|
||||
hostId: 'host-1',
|
||||
hostLabel: 'Prod',
|
||||
hostname: 'prod.example.com',
|
||||
username: 'deploy',
|
||||
status: 'connected',
|
||||
protocol: 'ssh',
|
||||
port: 22,
|
||||
moshEnabled: false,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const popupPayload = (sourceSessionValue: TerminalSession): TerminalPopupPayload => ({
|
||||
title: 'Prod · docker: api',
|
||||
parentSessionId: sourceSessionValue.id,
|
||||
sourceSession: sourceSessionValue,
|
||||
startupCommand: 'docker exec -it abc123 sh',
|
||||
});
|
||||
|
||||
test('resolveTerminalPopupHost honors source session transport over saved Mosh host settings', () => {
|
||||
const host = resolveTerminalPopupHost(popupPayload(sourceSession()), [vaultHost()]);
|
||||
|
||||
assert.equal(host.id, 'host-1');
|
||||
assert.equal(host.hostname, 'prod.example.com');
|
||||
assert.equal(host.protocol, 'ssh');
|
||||
assert.equal(host.moshEnabled, false);
|
||||
assert.equal(host.etEnabled, false);
|
||||
});
|
||||
|
||||
test('resolveTerminalPopupHost still falls back to source session details when the host is missing', () => {
|
||||
const host = resolveTerminalPopupHost(popupPayload(sourceSession({ hostId: 'missing' })), []);
|
||||
|
||||
assert.equal(host.id, 'missing');
|
||||
assert.equal(host.label, 'Prod');
|
||||
assert.equal(host.protocol, 'ssh');
|
||||
assert.equal(host.moshEnabled, false);
|
||||
});
|
||||
|
||||
test('resolveTerminalPopupHost preserves plugin transport and configuration for copied windows', () => {
|
||||
const providerId = 'com.example.transport.connection';
|
||||
const protocol = `plugin:${providerId}` as const;
|
||||
const pluginConnection = {
|
||||
providerId,
|
||||
authenticationProviderId: 'com.example.transport.auth',
|
||||
configuration: { endpoint: 'gateway.example', features: ['importers'] },
|
||||
credentialId: 'credential-reference-1234',
|
||||
};
|
||||
|
||||
const host = resolveTerminalPopupHost(
|
||||
popupPayload(sourceSession({
|
||||
protocol,
|
||||
pluginConnection,
|
||||
hostId: 'missing-plugin-host',
|
||||
hostname: 'provider-placeholder.example',
|
||||
})),
|
||||
[],
|
||||
);
|
||||
|
||||
assert.equal(host.protocol, protocol);
|
||||
assert.deepEqual(host.pluginConnection, pluginConnection);
|
||||
assert.notEqual(host.pluginConnection, pluginConnection);
|
||||
});
|
||||
|
||||
test('resolveTerminalPopupHost does not turn command popups into serial sessions without serial config', () => {
|
||||
const host = resolveTerminalPopupHost(
|
||||
popupPayload(sourceSession({ protocol: 'serial' })),
|
||||
[vaultHost({ protocol: 'serial' })],
|
||||
);
|
||||
|
||||
assert.equal(host.protocol, 'ssh');
|
||||
assert.equal(host.moshEnabled, false);
|
||||
});
|
||||
|
||||
test('resolveTerminalPopupHost preserves serial settings when attaching a live session', () => {
|
||||
const serialConfig = {
|
||||
path: '/dev/ttyUSB0',
|
||||
baudRate: 115200,
|
||||
lineMode: true,
|
||||
localEcho: true,
|
||||
} as const;
|
||||
const payload = popupPayload(sourceSession({
|
||||
protocol: 'serial',
|
||||
hostname: serialConfig.path,
|
||||
port: serialConfig.baudRate,
|
||||
serialConfig,
|
||||
}));
|
||||
payload.attachSessionId = payload.sourceSession.id;
|
||||
|
||||
const host = resolveTerminalPopupHost(payload, [vaultHost({ protocol: 'serial', serialConfig })]);
|
||||
|
||||
assert.equal(host.protocol, 'serial');
|
||||
assert.deepEqual(host.serialConfig, serialConfig);
|
||||
assert.match(source, /serialConfig=\{isAttachMode \? config\.sourceSession\.serialConfig : undefined\}/);
|
||||
});
|
||||
|
||||
test('resolveTerminalPopupReuseId uses the explicit reuse id from the prepared source session', () => {
|
||||
assert.equal(
|
||||
resolveTerminalPopupReuseId(popupPayload(sourceSession({ reuseConnectionFromSessionId: 'session-1' }))),
|
||||
'session-1',
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
resolveTerminalPopupReuseId(popupPayload(sourceSession({ reuseConnectionFromSessionId: undefined }))),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test('popup terminals resolve complete host config and pass jump hosts into Terminal', () => {
|
||||
assert.match(source, /proxyProfiles,\s+knownHosts,\s+snippets,\s+snippetPackages,\s+groupConfigs,/);
|
||||
assert.match(source, /deleteSelectedSnippets,/);
|
||||
assert.match(source, /resolveTerminalPopupHost\(config,\s*hosts,\s*\{\s+groupConfigs,\s+proxyProfiles,/);
|
||||
assert.match(source, /resolveTerminalChainHosts\(\{\s+host,\s+hosts,\s+groupConfigs,\s+proxyProfiles,/);
|
||||
assert.match(source, /chainHosts=\{chainHosts\}/);
|
||||
// Popup has no AppSideEffects listener; bulk delete must hit this vault instance.
|
||||
assert.match(source, /onDeleteSnippets=\{deleteSelectedSnippets\}/);
|
||||
});
|
||||
|
||||
test('popup terminals use their window-local vault readiness', () => {
|
||||
assert.match(source, /vaultInitializedOverride=\{vaultInitialized\}/);
|
||||
});
|
||||
|
||||
test('popup provider tree mounts the plugin authentication host', () => {
|
||||
assert.match(source, /import \{ PluginAuthenticationHost \} from '\.\/plugins\/PluginAuthenticationHost';/);
|
||||
assert.match(
|
||||
source,
|
||||
/<I18nProvider locale=\{settings\.uiLanguage\}>\s+<TerminalPopupPageInner\s+settings=\{settings\}\s+allowTerminalStart=\{allowTerminalStart\}\s+\/>\s+<PluginAuthenticationHost \/>\s+<\/I18nProvider>/,
|
||||
);
|
||||
});
|
||||
|
||||
test('attach popup close preparation has a bounded timeout', () => {
|
||||
assert.match(source, /Promise\.race\(\[/);
|
||||
assert.match(source, /Attach close preparation timed out/);
|
||||
assert.match(source, /1500/);
|
||||
});
|
||||
|
||||
test('terminal exit auto-close setting reaches tabs, workspaces, and popups', () => {
|
||||
assert.match(
|
||||
terminalLayerSource,
|
||||
/resolveTerminalSessionExitIntent\(\s+evt,\s+terminalSettings\?\.autoCloseOnExit \?\? true,\s+\)/,
|
||||
);
|
||||
assert.match(
|
||||
terminalSource,
|
||||
/const onSessionExitRef = useRef\(onSessionExit\);[\s\S]*?useLayoutEffect\(\(\) => \{\s+onSessionExitRef\.current = onSessionExit;\s+\}, \[onSessionExit\]\);/,
|
||||
);
|
||||
assert.match(
|
||||
terminalSource,
|
||||
/onSessionExitRef\.current\?\.\(closedSessionId, evt\)/,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/shouldCloseTerminalPopupOnExit\(evt, \{\s+autoCloseOnExit: settings\.terminalSettings\.autoCloseOnExit,\s+isAttachMode,\s+\}\)/,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/shouldRevealTerminalPopupOnExit\(evt, \{\s+autoCloseOnExit: settings\.terminalSettings\.autoCloseOnExit,\s+isAttachMode,\s+\}\)[\s\S]*?revealTerminal\(\);\s+return;[\s\S]*?setStartupError/,
|
||||
);
|
||||
});
|
||||
|
||||
test('an attached observe popup never owns automatic reconnect', () => {
|
||||
const reconnectStart = terminalSource.indexOf('const scheduleAutoReconnect = useCallback');
|
||||
const reconnectEnd = terminalSource.indexOf('const prepareRestoredReconnect', reconnectStart);
|
||||
const reconnectSource = terminalSource.slice(reconnectStart, reconnectEnd);
|
||||
assert.match(reconnectSource, /if \(attachExistingSession\) return false;/);
|
||||
assert.match(reconnectSource, /\[attachExistingSession, host, t, terminalSettings, updateStatus\]/);
|
||||
assert.match(terminalSource, /const startReconnect = async[\s\S]*?if \(attachExistingSession\) return;/);
|
||||
assert.match(
|
||||
terminalSource,
|
||||
/useEffect\(\(\) => \{\s+if \(attachExistingSession\) return undefined;\s+return terminalReconnectRegistry\.register/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user