Files
NetMesh/components/TerminalPopupPage.test.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

197 lines
7.5 KiB
TypeScript

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/,
);
});