mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-09 15:58:34 +00:00
12a3ae07ef
* Add local shell side panel auto-open settings with i18n support - Add settings to control side panel behavior for local shell sessions - Support auto-opening side panel when local shell is launched - Allow users to choose which pane (Files, SFTP, Scripts, etc.) opens by default - Add complete i18n translations for en, zh-CN, zh-TW, es, ru locales * fix: wire local shell side panel settings * fix: restore undefined check for local shell protocol detection Local shell sessions may have protocol=undefined, not just 'local'. Restore the original condition to auto-open sidebar for both cases. * fix: enable SFTP sidebar for local shell sessions Local shell now supports SFTP (for local file browsing). Update sftpAvailable to include local shell so the auto-open sidebar setting works correctly for PowerShell/bash sessions. * chore: bump version to 1.1.83-fork.1 Distinguish fork from upstream (1.1.83) and previous 0.0.0 dev version. * feat: show SFTP button in local shell toolbar Local shell sessions now display the 'Open SFTP' button in the toolbar, allowing users to browse local files via SFTP sidebar, consistent with the auto-open behavior added earlier. * feat: 本地Shell侧边栏支持追随终端目录功能 - 追随终端目录现在支持本地 POSIX shell(bash/zsh/fish) - 排除 PowerShell 和 cmd(因为它们的 cd 命令和路径格式不同) - 串口连接仍然不支持(它们根本没有 SFTP) - 定位路径到终端功能已支持本地 Shell(无需修改) * feat: support PowerShell and CMD in session restore - Accept Windows absolute paths (C:\...) in isRestoreCwdPathEligible - Add shell-specific cd commands: * PowerShell: Set-Location -LiteralPath with '' quote escaping * CMD: cd /d with "" quote escaping * bash/POSIX: cd -- with '\'' quote escaping - Remove PowerShell/CMD restrictions in cwd injection eligibility - Fix line terminator in SFTP locate path (use \r\n for Windows PTY) * fix: bound local shell panel preferences and synchronize settings --------- Co-authored-by: HHXXYY123 <HHXXYY123@outlook.com> Co-authored-by: binaricat <binaricat@users.noreply.github.com> Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
80 lines
3.5 KiB
TypeScript
80 lines
3.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import React, { useState } from 'react';
|
|
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
|
import { useSettingsIpcSync } from './settingsIpcSync.ts';
|
|
import { useSettingsStorageSync } from './settingsStorageSync.ts';
|
|
import type { TerminalSidePanelAutoOpenTab } from '../../domain/terminalSidePanelAutoOpen.ts';
|
|
import {
|
|
STORAGE_KEY_LOCAL_SHELL_SIDE_PANEL_AUTO_OPEN as enabledKey,
|
|
STORAGE_KEY_LOCAL_SHELL_SIDE_PANEL_AUTO_OPEN_TAB as tabKey,
|
|
} from '../../infrastructure/config/storageKeys.ts';
|
|
|
|
for (const pathway of ['ipc', 'storage'] as const) {
|
|
test(`${pathway} receives local shell settings, rejects invalid tabs and cleans up`, async (t) => {
|
|
let receive: ((payload: { key: string; value: unknown }) => void) | undefined;
|
|
let unsubscribed = false;
|
|
const fakeWindow = Object.assign(new EventTarget(), {
|
|
netcatty: {
|
|
onSettingsChanged(callback: typeof receive) {
|
|
receive = callback;
|
|
return () => { unsubscribed = true; };
|
|
},
|
|
},
|
|
});
|
|
for (const [key, value] of Object.entries({ window: fakeWindow, IS_REACT_ACT_ENVIRONMENT: true })) {
|
|
const previous = Object.getOwnPropertyDescriptor(globalThis, key);
|
|
Object.defineProperty(globalThis, key, { value, configurable: true });
|
|
t.after(() => {
|
|
if (previous) Object.defineProperty(globalThis, key, previous);
|
|
else Reflect.deleteProperty(globalThis, key);
|
|
});
|
|
}
|
|
let current = { enabled: false, tab: 'scripts' };
|
|
let renderer: ReactTestRenderer | undefined;
|
|
function Harness() {
|
|
const [enabled, setEnabled] = useState(false);
|
|
const [tab, setTab] = useState<TerminalSidePanelAutoOpenTab>('scripts');
|
|
current = { enabled, tab };
|
|
const params = {
|
|
localShellSidePanelAutoOpen: enabled,
|
|
localShellSidePanelAutoOpenTab: tab,
|
|
setLocalShellSidePanelAutoOpenState: setEnabled,
|
|
setLocalShellSidePanelAutoOpenTabState: setTab,
|
|
};
|
|
// Only local-shell events are dispatched; unrelated settings are not used.
|
|
useSettingsIpcSync({ ...params, enabled: pathway === 'ipc' } as unknown as Parameters<typeof useSettingsIpcSync>[0]);
|
|
useSettingsStorageSync({ ...params, enabled: pathway === 'storage' } as Parameters<typeof useSettingsStorageSync>[0]);
|
|
return null;
|
|
}
|
|
async function send(key: string, value: unknown) {
|
|
await act(async () => {
|
|
if (pathway === 'ipc') receive!({ key, value });
|
|
else fakeWindow.dispatchEvent(Object.assign(new Event('storage'), { key, newValue: value === null ? null : String(value) }));
|
|
});
|
|
}
|
|
try {
|
|
await act(async () => { renderer = create(React.createElement(Harness)); });
|
|
await send(enabledKey, true);
|
|
await send(tabKey, 'notes');
|
|
assert.deepEqual(current, { enabled: true, tab: 'notes' });
|
|
await send(tabKey, 'invalid');
|
|
await send(tabKey, null);
|
|
await send(enabledKey, null);
|
|
assert.deepEqual(current, { enabled: true, tab: 'notes' });
|
|
if (pathway === 'ipc') {
|
|
await send(enabledKey, 'false');
|
|
assert.equal(current.enabled, true);
|
|
}
|
|
await send(enabledKey, false);
|
|
await send(tabKey, 'history');
|
|
assert.deepEqual(current, { enabled: false, tab: 'history' });
|
|
await act(async () => { renderer!.unmount(); });
|
|
renderer = undefined;
|
|
if (pathway === 'ipc') assert.equal(unsubscribed, true);
|
|
} finally {
|
|
await act(async () => { renderer?.unmount(); });
|
|
}
|
|
});
|
|
}
|