mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-11 17:00:22 +00:00
* Codex worktree snapshot: startup-cleanup Co-authored-by: Codex * fix(browser): close malformed proxy connections without a reply * feat(browser): embed host browsing in terminal side panels * style(browser): blend compact browser chrome into terminal panels * feat(browser): open terminal links internally with a system browser preference * fix(browser): retain the first terminal link during panel mount replay * fix(browser): release contexts when their split pane closes * style(browser): remove the downloads panel shadow * fix(browser): ignore late downloads after panel closure * fix(browser): mount browser content on terminal auto-open * fix(browser): preserve empty-vault recovery with bookmarks * fix(browser): expose actions for supported terminal types * fix(browser): retain open requests before the panel mounts * fix(browser): align vault actions with supported hosts * fix(browser): share download controls across host contexts * fix(browser): keep vault browser requests on visible sessions * fix(browser): release contexts after workspace detach * fix(browser): enforce WebRTC routing on page contents * fix(browser): skip unsupported sessions in open requests * style(browser): encode glyphs in ASCII-safe source
153 lines
5.3 KiB
TypeScript
153 lines
5.3 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,
|
|
createSidePanelLayout,
|
|
splitSidePanelPane,
|
|
type SidePanelLayout,
|
|
} from '../../domain/sidePanelLayout.ts';
|
|
import { STORAGE_KEY_WORKSPACE_LAYOUT_PRESET } from '../../infrastructure/config/storageKeys.ts';
|
|
import { useWorkspaceLayoutPresetState } from './useWorkspaceLayoutPresetState.ts';
|
|
|
|
function buildSplitLayout(): SidePanelLayout {
|
|
let layout = createSidePanelLayout('scripts', 'pane-scripts');
|
|
layout = splitSidePanelPane(layout, 'pane-scripts', 'sftp', 'horizontal', {
|
|
paneId: 'pane-sftp',
|
|
splitId: 'split-root',
|
|
}, 400);
|
|
return layout;
|
|
}
|
|
|
|
test('saved default resolves per session with fresh ids and protocol-aware pruning', async () => {
|
|
const values = new Map<string, string>();
|
|
const fakeStorage = {
|
|
getItem: (key: string) => values.get(key) ?? null,
|
|
setItem: (key: string, value: string) => { values.set(key, value); },
|
|
removeItem: (key: string) => { values.delete(key); },
|
|
};
|
|
const globals = globalThis as typeof globalThis & {
|
|
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
|
localStorage?: typeof fakeStorage;
|
|
};
|
|
const previousActEnvironment = globals.IS_REACT_ACT_ENVIRONMENT;
|
|
const previousLocalStorage = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
|
|
globals.IS_REACT_ACT_ENVIRONMENT = true;
|
|
Object.defineProperty(globalThis, 'localStorage', {
|
|
value: fakeStorage,
|
|
configurable: true,
|
|
});
|
|
|
|
let state: ReturnType<typeof useWorkspaceLayoutPresetState> | null = null;
|
|
const Probe = () => {
|
|
state = useWorkspaceLayoutPresetState();
|
|
return null;
|
|
};
|
|
|
|
try {
|
|
await act(async () => {
|
|
create(React.createElement(Probe));
|
|
});
|
|
assert.ok(state);
|
|
|
|
// Nothing saved yet: no preset to apply.
|
|
assert.equal(state.resolveDefaultLayoutForSession({ protocol: 'ssh' }), null);
|
|
|
|
let saved = false;
|
|
await act(async () => {
|
|
saved = state?.saveWorkspaceLayoutAsDefault(buildSplitLayout()) ?? false;
|
|
});
|
|
assert.equal(saved, true);
|
|
assert.ok(values.has(STORAGE_KEY_WORKSPACE_LAYOUT_PRESET));
|
|
|
|
// SSH sessions keep their SFTP pane; ids are rekeyed per application.
|
|
const sshResolved = state.resolveDefaultLayoutForSession({ protocol: 'ssh' });
|
|
assert.ok(sshResolved);
|
|
assert.equal(sshResolved.protocol, 'ssh');
|
|
assert.deepEqual(
|
|
collectSidePanelPanes(sshResolved.layout.root).map((pane) => pane.tool),
|
|
['scripts', 'sftp'],
|
|
);
|
|
assert.notEqual(sshResolved.layout.root.id, buildSplitLayout().root.id);
|
|
assert.equal(sshResolved.focusedTool, 'sftp');
|
|
|
|
// Protocols that cannot serve SFTP lose that pane.
|
|
const telnetResolved = state.resolveDefaultLayoutForSession({ protocol: 'telnet' });
|
|
assert.ok(telnetResolved);
|
|
assert.deepEqual(
|
|
collectSidePanelPanes(telnetResolved.layout.root).map((pane) => pane.tool),
|
|
['scripts'],
|
|
);
|
|
assert.equal(telnetResolved.focusedTool, 'scripts');
|
|
|
|
// A second resolve rekeys again so parallel workspaces share no node ids.
|
|
const again = state.resolveDefaultLayoutForSession({ protocol: 'ssh' });
|
|
assert.ok(again);
|
|
assert.notEqual(again.layout.root.id, sshResolved.layout.root.id);
|
|
await act(async () => {
|
|
state?.saveWorkspaceLayoutAsDefault(createSidePanelLayout('browser', 'browser-pane'));
|
|
});
|
|
assert.equal(state.resolveDefaultLayoutForSession({ protocol: 'telnet' }), null);
|
|
for (const protocol of ['ssh', 'mosh', 'local']) {
|
|
assert.equal(state.resolveDefaultLayoutForSession({ protocol })?.focusedTool, 'browser');
|
|
}
|
|
} finally {
|
|
if (previousLocalStorage) {
|
|
Object.defineProperty(globalThis, 'localStorage', previousLocalStorage);
|
|
} else {
|
|
delete (globalThis as { localStorage?: unknown }).localStorage;
|
|
}
|
|
globals.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
|
}
|
|
});
|
|
|
|
test('saving falls back to false when persistence rejects the write', async () => {
|
|
const fakeStorage = {
|
|
getItem: () => null,
|
|
setItem: () => {
|
|
const quotaError = new DOMException('full', 'QuotaExceededError');
|
|
throw quotaError;
|
|
},
|
|
removeItem: () => undefined,
|
|
};
|
|
const globals = globalThis as typeof globalThis & {
|
|
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
|
localStorage?: typeof fakeStorage;
|
|
};
|
|
const previousActEnvironment = globals.IS_REACT_ACT_ENVIRONMENT;
|
|
const previousLocalStorage = Object.getOwnPropertyDescriptor(globalThis, 'localStorage');
|
|
globals.IS_REACT_ACT_ENVIRONMENT = true;
|
|
Object.defineProperty(globalThis, 'localStorage', {
|
|
value: fakeStorage,
|
|
configurable: true,
|
|
});
|
|
|
|
let state: ReturnType<typeof useWorkspaceLayoutPresetState> | null = null;
|
|
const Probe = () => {
|
|
state = useWorkspaceLayoutPresetState();
|
|
return null;
|
|
};
|
|
|
|
try {
|
|
await act(async () => {
|
|
create(React.createElement(Probe));
|
|
});
|
|
assert.ok(state);
|
|
|
|
let saved = true;
|
|
await act(async () => {
|
|
saved = state?.saveWorkspaceLayoutAsDefault(buildSplitLayout()) ?? true;
|
|
});
|
|
assert.equal(saved, false);
|
|
} finally {
|
|
if (previousLocalStorage) {
|
|
Object.defineProperty(globalThis, 'localStorage', previousLocalStorage);
|
|
} else {
|
|
delete (globalThis as { localStorage?: unknown }).localStorage;
|
|
}
|
|
globals.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
|
}
|
|
});
|