Files
NetMesh/domain/sidePanelLayout.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

169 lines
6.3 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
MAX_SIDE_PANEL_PANES,
canSplitSidePanelPaneAtSize,
closeSidePanelPane,
collectSidePanelPanes,
createSidePanelLayout,
focusSidePanelPane,
getSidePanelNodeMinimumPixels,
getSidePanelSplitResizeBounds,
resizeSidePanelSplit,
selectSidePanelTool,
sidePanelLayoutHasTool,
splitSidePanelPane,
} from './sidePanelLayout.ts';
test('panes smaller than two minimum cells cannot be split again', () => {
const layout = createSidePanelLayout('notes', 'pane-notes');
const tooSmall = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 160);
assert.equal(canSplitSidePanelPaneAtSize(160), false);
assert.equal(canSplitSidePanelPaneAtSize(161), true);
assert.equal(tooSmall, layout);
});
test('nested split resize bounds preserve every descendant pane minimum', () => {
let layout = createSidePanelLayout('notes', 'pane-notes');
layout = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 420);
layout = splitSidePanelPane(layout, 'pane-ai', 'system', 'vertical', {
paneId: 'pane-system',
splitId: 'split-nested',
}, 210);
assert.equal(layout.root.type, 'split');
if (layout.root.type !== 'split') return;
assert.equal(getSidePanelNodeMinimumPixels(layout.root, 'vertical'), 242);
assert.equal(getSidePanelNodeMinimumPixels(layout.root, 'horizontal'), 80);
const bounds = getSidePanelSplitResizeBounds(layout.root, 0, 1, 419);
assert.ok(bounds.firstMax < 0.62);
assert.ok((1 - bounds.firstMax) * 419 >= 161);
});
test('a focused pane can be split repeatedly into a nested multi-pane layout', () => {
let layout = createSidePanelLayout('notes', 'pane-notes');
layout = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 400);
layout = splitSidePanelPane(layout, 'pane-ai', 'system', 'horizontal', {
paneId: 'pane-system',
splitId: 'split-nested',
}, 400);
assert.equal(layout.root.type, 'split');
assert.equal(layout.root.direction, 'vertical');
assert.equal(layout.root.children[1]?.type, 'split');
assert.equal(layout.root.children[1]?.type === 'split' ? layout.root.children[1].direction : null, 'horizontal');
assert.deepEqual(collectSidePanelPanes(layout.root).map((pane) => pane.tool), [
'notes',
'ai',
'system',
]);
assert.equal(layout.focusedPaneId, 'pane-system');
});
test('splitting with an occupied tool focuses its existing pane without duplicating it', () => {
let layout = createSidePanelLayout('notes', 'pane-notes');
layout = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 400);
const next = splitSidePanelPane(layout, 'pane-ai', 'notes', 'horizontal', {
paneId: 'unused-pane',
splitId: 'unused-split',
}, 400);
assert.equal(next.focusedPaneId, 'pane-notes');
assert.equal(collectSidePanelPanes(next.root).length, 2);
assert.equal(collectSidePanelPanes(next.root).filter((pane) => pane.tool === 'notes').length, 1);
});
test('the shared tool selection changes only the focused pane or focuses an existing tool', () => {
let layout = createSidePanelLayout('notes', 'pane-notes');
layout = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 400);
layout = focusSidePanelPane(layout, 'pane-notes');
const replaced = selectSidePanelTool(layout, 'system');
assert.deepEqual(collectSidePanelPanes(replaced.root).map((pane) => pane.tool), ['system', 'ai']);
assert.equal(replaced.focusedPaneId, 'pane-notes');
const focusedExisting = selectSidePanelTool(replaced, 'ai');
assert.deepEqual(collectSidePanelPanes(focusedExisting.root).map((pane) => pane.tool), ['system', 'ai']);
assert.equal(focusedExisting.focusedPaneId, 'pane-ai');
});
test('closing panes collapses their parent split and closing the last pane closes the layout', () => {
let layout = createSidePanelLayout('notes', 'pane-notes');
layout = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 400);
layout = splitSidePanelPane(layout, 'pane-ai', 'system', 'horizontal', {
paneId: 'pane-system',
splitId: 'split-nested',
}, 400);
const withoutAi = closeSidePanelPane(layout, 'pane-ai');
assert.ok(withoutAi);
assert.deepEqual(collectSidePanelPanes(withoutAi.root).map((pane) => pane.tool), ['notes', 'system']);
assert.equal(withoutAi.focusedPaneId, 'pane-system');
const single = closeSidePanelPane(withoutAi, 'pane-system');
assert.ok(single);
assert.equal(single.root.type, 'pane');
assert.equal(single.root.type === 'pane' ? single.root.tool : null, 'notes');
assert.equal(closeSidePanelPane(single, 'pane-notes'), null);
});
test('split sizes can be updated without changing any nested pane', () => {
let layout = createSidePanelLayout('notes', 'pane-notes');
layout = splitSidePanelPane(layout, 'pane-notes', 'ai', 'vertical', {
paneId: 'pane-ai',
splitId: 'split-root',
}, 400);
const resized = resizeSidePanelSplit(layout, 'split-root', [3, 1]);
assert.equal(resized.root.type, 'split');
assert.deepEqual(resized.root.type === 'split' ? resized.root.sizes : [], [0.75, 0.25]);
assert.equal(sidePanelLayoutHasTool(resized, 'ai'), true);
});
test('the pane limit rejects additional splits without changing the layout', () => {
let layout = createSidePanelLayout('sftp', 'pane-0');
const tools = ['scripts', 'history', 'theme', 'system', 'notes', 'ai'] as const;
tools.forEach((tool, index) => {
layout = splitSidePanelPane(layout, `pane-${index}`, tool, 'vertical', {
paneId: `pane-${index + 1}`,
splitId: `split-${index}`,
}, 400);
}, 400);
// The product currently has seven unique tools, but the domain limit remains
// explicit so future tools cannot create an unbounded layout.
assert.equal(MAX_SIDE_PANEL_PANES, 8);
assert.equal(collectSidePanelPanes(layout.root).length, 7);
const duplicateAttempt = splitSidePanelPane(layout, 'pane-6', 'sftp', 'horizontal', {
paneId: 'pane-extra',
splitId: 'split-extra',
}, 400);
assert.equal(collectSidePanelPanes(duplicateAttempt.root).length, 7);
assert.equal(duplicateAttempt.focusedPaneId, 'pane-0');
});