133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
|
|
import assert from 'node:assert/strict';
|
||
|
|
import test from 'node:test';
|
||
|
|
import React from 'react';
|
||
|
|
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||
|
|
|
||
|
|
import { collectSidePanelPanes } from '../../domain/sidePanelLayout.ts';
|
||
|
|
import { useTerminalSidePanelLayoutState } from './useTerminalSidePanelLayoutState.ts';
|
||
|
|
|
||
|
|
test('terminal side panel layouts stay isolated and keep one pane per tool', async () => {
|
||
|
|
const actEnvironment = globalThis as typeof globalThis & {
|
||
|
|
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
||
|
|
};
|
||
|
|
const previousActEnvironment = actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
||
|
|
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
||
|
|
let state: ReturnType<typeof useTerminalSidePanelLayoutState> | null = null;
|
||
|
|
let renderer: ReactTestRenderer | null = null;
|
||
|
|
|
||
|
|
const Probe = () => {
|
||
|
|
state = useTerminalSidePanelLayoutState();
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
await act(async () => {
|
||
|
|
renderer = create(React.createElement(Probe));
|
||
|
|
});
|
||
|
|
await act(async () => {
|
||
|
|
state?.setSidePanelOpenTabs(new Map([
|
||
|
|
['terminal-a', 'notes'],
|
||
|
|
['terminal-b', 'scripts'],
|
||
|
|
]));
|
||
|
|
});
|
||
|
|
|
||
|
|
const layoutA = state?.sidePanelLayouts.get('terminal-a');
|
||
|
|
const layoutB = state?.sidePanelLayouts.get('terminal-b');
|
||
|
|
assert.ok(layoutA);
|
||
|
|
assert.ok(layoutB);
|
||
|
|
|
||
|
|
await act(async () => {
|
||
|
|
state?.splitPane('terminal-a', 'ai', 'vertical', {
|
||
|
|
paneId: 'pane-ai',
|
||
|
|
splitId: 'split-a',
|
||
|
|
}, 400);
|
||
|
|
});
|
||
|
|
assert.deepEqual(
|
||
|
|
collectSidePanelPanes(state!.sidePanelLayouts.get('terminal-a')!.root).map((pane) => pane.tool),
|
||
|
|
['notes', 'ai'],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
collectSidePanelPanes(state!.sidePanelLayouts.get('terminal-b')!.root).map((pane) => pane.tool),
|
||
|
|
['scripts'],
|
||
|
|
);
|
||
|
|
|
||
|
|
await act(async () => {
|
||
|
|
state?.splitPane('terminal-a', 'notes', 'horizontal', {
|
||
|
|
paneId: 'unused-pane',
|
||
|
|
splitId: 'unused-split',
|
||
|
|
}, 400);
|
||
|
|
});
|
||
|
|
const focused = state!.sidePanelLayouts.get('terminal-a')!;
|
||
|
|
assert.equal(collectSidePanelPanes(focused.root).length, 2);
|
||
|
|
assert.equal(state!.sidePanelOpenTabs.get('terminal-a'), 'notes');
|
||
|
|
|
||
|
|
// External open paths still write the focused-tool map. They must replace
|
||
|
|
// only the focused pane, then focus an existing pane without duplicating it.
|
||
|
|
await act(async () => {
|
||
|
|
state?.setSidePanelOpenTabs((current) => new Map(current).set('terminal-a', 'system'));
|
||
|
|
});
|
||
|
|
assert.deepEqual(
|
||
|
|
collectSidePanelPanes(state!.sidePanelLayouts.get('terminal-a')!.root).map((pane) => pane.tool),
|
||
|
|
['system', 'ai'],
|
||
|
|
);
|
||
|
|
await act(async () => {
|
||
|
|
state?.setSidePanelOpenTabs((current) => new Map(current).set('terminal-a', 'ai'));
|
||
|
|
});
|
||
|
|
const externallyFocused = state!.sidePanelLayouts.get('terminal-a')!;
|
||
|
|
assert.deepEqual(collectSidePanelPanes(externallyFocused.root).map((pane) => pane.tool), ['system', 'ai']);
|
||
|
|
assert.equal(state!.sidePanelOpenTabs.get('terminal-a'), 'ai');
|
||
|
|
assert.equal(
|
||
|
|
collectSidePanelPanes(externallyFocused.root).find((pane) => pane.id === externallyFocused.focusedPaneId)?.tool,
|
||
|
|
'ai',
|
||
|
|
);
|
||
|
|
} finally {
|
||
|
|
await act(async () => {
|
||
|
|
renderer?.unmount();
|
||
|
|
});
|
||
|
|
actEnvironment.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('closing the final pane removes only that terminal side panel state', async () => {
|
||
|
|
const actEnvironment = globalThis as typeof globalThis & {
|
||
|
|
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
||
|
|
};
|
||
|
|
const previousActEnvironment = actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
||
|
|
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
||
|
|
let state: ReturnType<typeof useTerminalSidePanelLayoutState> | null = null;
|
||
|
|
let renderer: ReactTestRenderer | null = null;
|
||
|
|
|
||
|
|
const Probe = () => {
|
||
|
|
state = useTerminalSidePanelLayoutState();
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
await act(async () => {
|
||
|
|
renderer = create(React.createElement(Probe));
|
||
|
|
});
|
||
|
|
await act(async () => {
|
||
|
|
state?.setSidePanelOpenTabs(new Map([
|
||
|
|
['terminal-a', 'notes'],
|
||
|
|
['terminal-b', 'scripts'],
|
||
|
|
]));
|
||
|
|
});
|
||
|
|
const paneId = state!.sidePanelLayouts.get('terminal-a')!.focusedPaneId;
|
||
|
|
|
||
|
|
let closedLast = false;
|
||
|
|
await act(async () => {
|
||
|
|
closedLast = state!.closePane('terminal-a', paneId);
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.equal(closedLast, true);
|
||
|
|
assert.equal(state!.sidePanelLayouts.has('terminal-a'), false);
|
||
|
|
assert.equal(state!.sidePanelOpenTabs.has('terminal-a'), false);
|
||
|
|
assert.equal(state!.sidePanelLayouts.has('terminal-b'), true);
|
||
|
|
} finally {
|
||
|
|
await act(async () => {
|
||
|
|
renderer?.unmount();
|
||
|
|
});
|
||
|
|
actEnvironment.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
||
|
|
}
|
||
|
|
});
|