Files
NetMesh/application/state/copyWorkspace.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

152 lines
5.3 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import type { TerminalSession, Workspace } from '../../domain/models';
import { collectSessionIds } from '../../domain/workspace';
import { buildCopiedWorkspace } from './useSessionState';
const session = (id: string, workspaceId?: string): TerminalSession => ({
id,
hostId: `host-${id}`,
hostLabel: `Host ${id}`,
hostname: `${id}.example.test`,
username: 'user',
status: 'connected',
protocol: 'ssh',
workspaceId,
});
const wsRoot = {
id: 'split-1',
type: 'split' as const,
direction: 'vertical' as const,
sizes: [0.6, 0.4],
children: [
{ id: 'pane-1', type: 'pane' as const, sessionId: 's1' },
{ id: 'pane-2', type: 'pane' as const, sessionId: 's2' },
],
};
const sourceWorkspace: Workspace = {
id: 'ws-src',
title: 'My Split',
viewMode: 'split',
focusedSessionId: 's2',
focusSessionOrder: ['s1', 's2'],
root: wsRoot,
};
test('buildCopiedWorkspace clones every session with the new workspace id and inherited cwd', () => {
const prev = [session('s1', 'ws-src'), session('s2', 'ws-src')];
const built = buildCopiedWorkspace(sourceWorkspace, prev, {
newWorkspaceId: 'ws-new',
sessionIdMap: new Map([['s1', 'n1'], ['s2', 'n2']]),
perPaneCwd: { s1: '/home/a', s2: '/home/b' },
});
assert.ok(built);
assert.deepEqual(built.newSessions.map(s => s.id), ['n1', 'n2']);
assert.ok(built.newSessions.every(s => s.workspaceId === 'ws-new'));
assert.equal(built.newSessions[0].pendingInitialCwd, '/home/a');
assert.equal(built.newSessions[1].pendingInitialCwd, '/home/b');
});
test('buildCopiedWorkspace rebuilds the tree with new ids and preserves view mode + remapped focus', () => {
const prev = [session('s1', 'ws-src'), session('s2', 'ws-src')];
const built = buildCopiedWorkspace(sourceWorkspace, prev, {
newWorkspaceId: 'ws-new',
sessionIdMap: new Map([['s1', 'n1'], ['s2', 'n2']]),
});
assert.ok(built);
assert.equal(built.newWorkspace.id, 'ws-new');
assert.equal(built.newWorkspace.viewMode, 'split');
assert.equal(built.newWorkspace.title, 'My Split');
assert.deepEqual(collectSessionIds(built.newWorkspace.root), ['n1', 'n2']);
assert.equal(built.newWorkspace.focusedSessionId, 'n2');
assert.deepEqual(built.newWorkspace.focusSessionOrder, ['n1', 'n2']);
const ids = collectSessionIds(built.newWorkspace.root);
assert.ok(!ids.includes('s1') && !ids.includes('s2'));
});
test('buildCopiedWorkspace prunes panes whose source session no longer exists', () => {
const prev = [session('s2', 'ws-src')]; // s1 was closed
const built = buildCopiedWorkspace(sourceWorkspace, prev, {
newWorkspaceId: 'ws-new',
sessionIdMap: new Map([['s1', 'n1'], ['s2', 'n2']]),
});
assert.ok(built);
assert.deepEqual(built.newSessions.map(s => s.id), ['n2']);
assert.deepEqual(collectSessionIds(built.newWorkspace.root), ['n2']);
assert.equal(built.newWorkspace.focusedSessionId, 'n2');
assert.deepEqual(built.newWorkspace.focusSessionOrder, ['n2']);
});
test('buildCopiedWorkspace returns null when no source session survives', () => {
const built = buildCopiedWorkspace(sourceWorkspace, [], {
newWorkspaceId: 'ws-new',
sessionIdMap: new Map([['s1', 'n1'], ['s2', 'n2']]),
});
assert.equal(built, null);
});
test('buildCopiedWorkspace routes inherited cwd to localStartDir for local sessions', () => {
const localSrc: Workspace = {
id: 'ws-src',
title: 'Local Split',
root: {
id: 'sp', type: 'split', direction: 'vertical',
children: [
{ id: 'p1', type: 'pane', sessionId: 'l1' },
{ id: 'p2', type: 'pane', sessionId: 'l2' },
],
},
};
const local = (id: string): TerminalSession => ({
id, hostId: `h-${id}`, hostLabel: 'Local', hostname: 'local',
username: 'user', status: 'connected', protocol: 'local',
localStartDir: '/', workspaceId: 'ws-src',
});
const built = buildCopiedWorkspace(localSrc, [local('l1'), local('l2')], {
newWorkspaceId: 'ws-new',
sessionIdMap: new Map([['l1', 'm1'], ['l2', 'm2']]),
perPaneCwd: { l1: '/home/a', l2: '/home/b' },
});
assert.ok(built);
assert.equal(built.newSessions[0].localStartDir, '/home/a');
assert.equal(built.newSessions[1].localStartDir, '/home/b');
assert.equal(built.newSessions[0].pendingInitialCwd, undefined);
});
test('buildCopiedWorkspace prunes multiple dead sessions across a nested tree', () => {
const nested: Workspace = {
id: 'ws-src', title: 'Nested',
root: {
id: 'r', type: 'split', direction: 'vertical',
children: [
{ id: 'pa', type: 'pane', sessionId: 'a' },
{
id: 'inner', type: 'split', direction: 'horizontal',
children: [
{ id: 'pb', type: 'pane', sessionId: 'b' },
{ id: 'pc', type: 'pane', sessionId: 'c' },
],
},
],
},
};
const s = (id: string): TerminalSession => ({
id, hostId: `h-${id}`, hostLabel: 'H', hostname: 'h',
username: 'u', status: 'connected', protocol: 'ssh', workspaceId: 'ws-src',
});
// Only 'b' survives; 'a' and 'c' are gone.
const built = buildCopiedWorkspace(nested, [s('b')], {
newWorkspaceId: 'ws-new',
sessionIdMap: new Map([['a', 'na'], ['b', 'nb'], ['c', 'nc']]),
});
assert.ok(built);
assert.deepEqual(built.newSessions.map(x => x.id), ['nb']);
assert.deepEqual(collectSessionIds(built.newWorkspace.root), ['nb']);
});