mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-11 17:00:22 +00:00
86 lines
4.2 KiB
TypeScript
86 lines
4.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import test from 'node:test';
|
|
import React, { act } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { JSDOM } from 'jsdom';
|
|
import { createLocalShellHost, resolveHostDefaultLocalShell } from '../../domain/localShellHost';
|
|
import { collectSessionIds } from '../../domain/workspace';
|
|
import { activeTabStore } from './activeTabStore';
|
|
import { useSessionState } from './useSessionState';
|
|
|
|
test('workspace host opening acknowledges committed insertion and rejects queued closure', async (t) => {
|
|
const dom = new JSDOM('<div id="root"></div>', { url: 'http://localhost' });
|
|
const globals = {
|
|
window: dom.window,
|
|
document: dom.window.document,
|
|
localStorage: dom.window.localStorage,
|
|
CustomEvent: dom.window.CustomEvent,
|
|
Event: dom.window.Event,
|
|
IS_REACT_ACT_ENVIRONMENT: true,
|
|
};
|
|
const previous = new Map<string, PropertyDescriptor | undefined>();
|
|
for (const [key, value] of Object.entries(globals)) {
|
|
previous.set(key, Object.getOwnPropertyDescriptor(globalThis, key));
|
|
Object.defineProperty(globalThis, key, { configurable: true, value });
|
|
}
|
|
const root = createRoot(document.getElementById('root')!);
|
|
t.after(async () => {
|
|
await act(async () => root.unmount());
|
|
dom.window.close();
|
|
for (const [key, descriptor] of previous) {
|
|
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
|
|
else Reflect.deleteProperty(globalThis, key);
|
|
}
|
|
activeTabStore.setActiveTabId('vault');
|
|
});
|
|
let state: ReturnType<typeof useSessionState>;
|
|
function Probe() {
|
|
state = useSessionState({ persistSessionRestore: false });
|
|
return null;
|
|
}
|
|
await act(async () => root.render(React.createElement(React.StrictMode, null, React.createElement(Probe))));
|
|
const host = createLocalShellHost({ id: 'local-inheriting' });
|
|
await act(async () => state.createWorkspaceWithHosts('Target', [host]));
|
|
const workspaceId = state!.workspaces[0].id;
|
|
const resolvedHost = resolveHostDefaultLocalShell(host, {
|
|
terminalSettings: { localShell: '/bin/zsh', localShellArgs: ['-l'], localStartDir: '/tmp/project' },
|
|
});
|
|
let ordinaryId: string | null = null;
|
|
await act(async () => {
|
|
ordinaryId = state.appendHostToWorkspace(workspaceId, resolvedHost);
|
|
});
|
|
assert.ok(ordinaryId);
|
|
assert.ok(state!.sessions.some(session => session.id === ordinaryId));
|
|
assert.ok(collectSessionIds(state!.workspaces[0].root).includes(ordinaryId));
|
|
let insertedId: string | null = null;
|
|
await act(async () => {
|
|
insertedId = state.appendHostToWorkspaceConfirmed(workspaceId, resolvedHost);
|
|
assert.ok(insertedId, 'success is returned after both state updates commit');
|
|
const session = state.sessions.find(s => s.id === insertedId);
|
|
assert.equal(session?.localShell, '/bin/zsh');
|
|
assert.deepEqual(session?.localShellArgs, ['-l']);
|
|
assert.equal(session?.localStartDir, '/tmp/project');
|
|
assert.equal(state.sessions.filter(s => s.id === insertedId).length, 1);
|
|
assert.equal(collectSessionIds(state.workspaces[0].root).filter(id => id === insertedId).length, 1);
|
|
});
|
|
await act(async () => {
|
|
// The rendered ref still contains the workspace; its close updater is queued.
|
|
state.closeWorkspace(workspaceId);
|
|
const result = state.appendHostToWorkspaceConfirmed(workspaceId, resolvedHost);
|
|
assert.equal(result, null, 'must not acknowledge an allocated but uninserted ID');
|
|
});
|
|
assert.equal(state!.workspaces.length, 0);
|
|
assert.equal(state!.sessions.length, 0, 'no orphan session survives the queued close');
|
|
assert.equal(activeTabStore.getActiveTabId(), 'vault');
|
|
await act(async () => {
|
|
assert.equal(state.appendHostToWorkspaceConfirmed(workspaceId, resolvedHost), null);
|
|
});
|
|
});
|
|
|
|
test('agent workspace host opening uses the common launch and history handler', () => {
|
|
const source = readFileSync(new URL('../app/AppSideEffects.tsx', import.meta.url), 'utf8');
|
|
assert.match(source, /const sessionId = handleConnectToHost\(host, true, hidden, target\.workspaceId\)/);
|
|
assert.match(source, /connectToHost: workspaceId\s*\? \(candidate: Host\) => appendHostToWorkspaceConfirmed\(workspaceId, candidate\)\s*: connectToHost/);
|
|
});
|