feat(settings): 将工具中心整合至设置中心

- 侧栏移除独立工具入口并统一使用设置齿轮
- 设置中心扩展为六组导航并复用原有工具内页
- 统一安全更新、连接配置、AI 设置与工具页面的返回路径
- 清理快捷键捕获、连接密码弹窗和 AI 修复上下文等瞬态状态
- 更新设置、侧栏、SQL 审计与国际化回归测试
This commit is contained in:
Syngnat
2026-07-16 11:17:59 +08:00
parent ac4b520295
commit 3de7e1bb40
10 changed files with 351 additions and 591 deletions

View File

@@ -13,9 +13,10 @@ const aiSettingsModalSource = readFileSync(
);
describe('settings center layout', () => {
it('uses the same split navigation shell as the tool center', () => {
expect(appSource).toContain("type SettingsCenterGroupKey = 'preferences' | 'services' | 'about';");
expect(appSource).toContain("type SettingsCenterPaneKey = 'language' | 'theme' | 'sidebar-metadata' | 'sidebar-objects' | 'proxy' | 'web-auth' | 'ai' | 'about-go-navi';");
it('hosts settings and tools in one split navigation shell', () => {
expect(appSource).toContain("type SettingsCenterGroupKey = 'preferences' | 'services' | ToolCenterGroupKey | 'about';");
expect(appSource).toContain('type SettingsCenterPaneKey =');
expect(appSource).toContain('| ToolCenterPaneKey');
expect(appSource).toContain("const [activeSettingsCenterGroupKey, setActiveSettingsCenterGroupKey] = useState<SettingsCenterGroupKey>('preferences');");
expect(appSource).toContain("const [activeSettingsCenterPane, setActiveSettingsCenterPane] = useState<SettingsCenterPaneState | null>(null);");
expect(appSource).toContain('style={toolCenterModalWorkspaceStyle}');
@@ -24,11 +25,17 @@ describe('settings center layout', () => {
expect(appSource).toContain('style={toolCenterNavScrollStyle}');
expect(appSource).toContain('style={toolCenterContentPanelStyle}');
expect(appSource).toContain('style={toolCenterDetailPanelStyle}');
expect(appSource).toContain('style={toolCenterDetailBodyStyle}');
expect(appSource).toContain('style={isActiveToolCenterPane ? toolCenterDetailBodyStyle : settingsCenterDetailBodyStyle}');
expect(appSource).toContain('style={toolCenterScrollableListStyle}');
expect(appSource).toContain("title: t('app.settings.group.preferences.title')");
expect(appSource).toContain("title: t('app.settings.group.services.title')");
expect(appSource).toContain("title: t('app.tools.group.config.title')");
expect(appSource).toContain("title: t('app.tools.group.workflow.title')");
expect(appSource).toContain("title: t('app.tools.group.workspace.title')");
expect(appSource).toContain("title: t('app.settings.group.about.title')");
expect(appSource).toContain('const combinedSettingsCenterGroups = [');
expect(appSource).not.toContain('const [isToolsModalOpen');
expect(appSource).not.toContain('{isToolsModalOpen &&');
});
it('moves sidebar table metadata configuration into the settings center', () => {
@@ -87,6 +94,43 @@ describe('settings center layout', () => {
expect(appSource).toContain("t('common.back_to_previous')");
});
it('clears embedded tool transient state before switching settings groups or panes', () => {
const cleanupStart = appSource.indexOf('const clearSettingsCenterTransientPaneState = useCallback(() => {');
const cleanupSource = appSource.slice(
cleanupStart,
appSource.indexOf('const handleOpenToolsModal', cleanupStart),
);
expect(cleanupStart).toBeGreaterThan(-1);
expect(cleanupSource).toContain('setCapturingShortcutAction(null);');
expect(cleanupSource).toContain("activeSettingsCenterPaneRef.current?.key === 'connection-package'");
expect(cleanupSource).toContain('closeConnectionPackageDialog();');
expect(cleanupSource).toContain("activeSettingsCenterPaneRef.current?.key === 'ai'");
expect(cleanupSource).toContain('setFocusedAIProviderId(undefined);');
expect(cleanupSource).toContain('setSecurityUpdateRepairSource(null);');
expect(appSource).toContain('const handleOpenSettingsModal = useCallback');
expect(appSource).toContain('const handleOpenToolCenterPane = useCallback');
expect(appSource.match(/clearSettingsCenterTransientPaneState\(\);/g)?.length).toBeGreaterThanOrEqual(4);
});
it('routes every security-update detail and repair return through settings center', () => {
const openDetailsStart = appSource.indexOf('const openSecurityUpdateSettings = useCallback(');
const openDetailsSource = appSource.slice(
openDetailsStart,
appSource.indexOf('const handleOpenSecurityUpdateSettings', openDetailsStart),
);
expect(openDetailsStart).toBeGreaterThan(-1);
expect(openDetailsSource).toContain("setActiveSettingsCenterGroupKey('config');");
expect(openDetailsSource).toContain("setActiveSettingsCenterPane({ key: 'security-update', group: 'config' });");
expect(openDetailsSource).toContain('setIsSettingsModalOpen(true);');
expect(appSource).toContain("const detailsWereOpen = isSettingsModalOpen && activeSettingsCenterPane?.key === 'security-update';");
expect(appSource.match(/<SecurityUpdateSettingsModal/g)?.length).toBe(1);
expect(appSource).toContain('<SecurityUpdateSettingsModal\n embedded');
expect(appSource).not.toContain('isSecurityUpdateSettingsOpen');
expect(appSource).not.toContain('setIsSecurityUpdateSettingsOpen');
});
it('uses instant-apply footer actions only on v2 theme settings pane', () => {
expect(appSource).toContain("isV2Ui && activeSettingsCenterPane.key === 'theme' ? (");
expect(appSource).toContain("t('common.close')");
@@ -141,7 +185,7 @@ describe('settings center layout', () => {
expect(appSource).toContain('const resolveSettingsCenterGroupInitialPane = (group: SettingsCenterGroupKey): SettingsCenterPaneState | null => (');
expect(appSource).toContain("group === 'about' ? { key: 'about-go-navi', group: 'about' } : null");
expect(appSource).toContain('setActiveSettingsCenterPane(resolveSettingsCenterGroupInitialPane(group));');
expect(appSource).toContain('setActiveSettingsCenterPane(resolveSettingsCenterGroupInitialPane(group.key));');
expect(appSource).toContain('handleOpenSettingsModal(group.key);');
});
it('routes silent update discovery to the settings center about pane via bridge', () => {
@@ -157,8 +201,10 @@ describe('settings center layout', () => {
expect(appSource).toContain('const renderSettingsCenterAboutPane = () => {');
expect(appSource).toContain('const renderSettingsCenterAboutProjectEntry = ({');
expect(appSource).toContain("padding: '18px 22px'");
expect(appSource).toContain('width: 64');
expect(appSource).toContain('height: 64');
expect(appSource).toContain('width={108}');
expect(appSource).toContain('height={108}');
expect(appSource).toContain('width: 108');
expect(appSource).toContain('height: 108');
expect(appSource).toContain('minWidth: 260');
expect(appSource).toContain('const releaseTimeText = formatAboutReleaseTime(lastUpdateInfo?.releasePublishedAt);');
expect(appSource).toContain("[t('app.about.version.release_time'), releaseTimeText]");
@@ -186,7 +232,7 @@ describe('settings center layout', () => {
expect(appSource).toContain("activeSettingsCenterPane?.key === 'theme' || activeSettingsCenterPane?.key === 'ai'");
expect(appSource).toContain('const settingsCenterDetailBodyStyle: React.CSSProperties = isSettingsCenterContainedScrollPane');
expect(appSource).toContain("overflowY: 'hidden'");
expect(appSource).toContain('style={settingsCenterDetailBodyStyle}');
expect(appSource).toContain('style={isActiveToolCenterPane ? toolCenterDetailBodyStyle : settingsCenterDetailBodyStyle}');
expect(appSource).toContain("boxSizing: 'border-box'");
expect(appSource).toContain("overscrollBehavior: 'contain'");
expect(aiSettingsModalSource).toContain("boxSizing: 'border-box'");

View File

@@ -45,15 +45,14 @@ const getGlobalShortcutCaseBlock = (action: string) => {
return afterCase.slice(0, endIndex);
};
describe('tool center menu entries', () => {
describe('settings center tool entries', () => {
it('exposes snippet management next to shortcut management', () => {
expect(appSource).toContain("key: 'snippet-settings'");
expect(appSource).toContain("title: t('app.tools.entry.snippets.title')");
expect(appSource).toContain("description: t('app.tools.entry.snippets.description')");
expect(appSource).toContain("handleOpenToolCenterPane('workspace', 'snippet-settings')");
expect(appSource).toContain('gonavi:open-snippet-settings');
expect(appSource).toContain("setIsSnippetModalOpen(false);");
expect(appSource).not.toContain('setIsSnippetModalOpen(true)');
expect(appSource).not.toContain('isSnippetModalOpen');
const snippetIndex = appSource.indexOf("key: 'snippet-settings'");
const shortcutIndex = appSource.indexOf("key: 'shortcut-settings'", snippetIndex);
@@ -61,14 +60,15 @@ describe('tool center menu entries', () => {
expect(shortcutIndex).toBeGreaterThan(snippetIndex);
});
it('uses scalable side navigation for the tool center instead of horizontal segmented switching', () => {
it('uses the settings center side navigation for every tool group', () => {
expect(appSource).toContain("type ToolCenterGroupKey = 'config' | 'workflow' | 'workspace';");
expect(appSource).toContain("const [activeToolCenterGroupKey, setActiveToolCenterGroupKey] = useState<ToolCenterGroupKey>('config');");
expect(appSource).toContain("type SettingsCenterGroupKey = 'preferences' | 'services' | ToolCenterGroupKey | 'about';");
expect(appSource).toContain("const [toolCenterBackGroupKey, setToolCenterBackGroupKey] = useState<ToolCenterGroupKey | null>(null);");
expect(appSource).toContain("title: t('app.tools.group.config.title')");
expect(appSource).toContain("title: t('app.tools.group.workflow.title')");
expect(appSource).toContain("title: t('app.tools.group.workspace.title')");
expect(appSource).toContain("filteredToolCenterGroups.find((group) => group.key === activeToolCenterGroupKey)");
expect(appSource).toContain('const combinedSettingsCenterGroups = [');
expect(appSource).toContain('(group) => group.key === activeSettingsCenterGroupKey');
expect(appUtilityStylesSource).toContain("const toolCenterModalSplitStyle = useMemo<React.CSSProperties>(() => ({");
expect(appUtilityStylesSource).toContain("gridTemplateColumns: '232px minmax(0, 1fr)'");
expect(appUtilityStylesSource).toContain("const toolCenterNavPanelStyle = useMemo<React.CSSProperties>(() => ({");
@@ -81,17 +81,18 @@ describe('tool center menu entries', () => {
expect(appSource).toContain('aria-selected={active}');
expect(appSource).toContain('title={`${group.title} - ${group.description}`}');
expect(appUtilityStylesSource).toContain("borderRight: `1px solid ${overlayTheme.divider}`");
expect(appSource).toContain('setActiveToolCenterPane(null);');
expect(appSource).toContain('setActiveSettingsCenterPane(null);');
expect(appSource).toContain('group.items.length');
expect(appSource).toContain("const handleOpenToolCenterPane = useCallback((group: ToolCenterGroupKey, key: ToolCenterPaneKey) => {");
expect(appSource).toContain("const [activeToolCenterPane, setActiveToolCenterPane] = useState<ToolCenterPaneState | null>(null);");
expect(appSource).toContain("const [activeSettingsCenterPane, setActiveSettingsCenterPane] = useState<SettingsCenterPaneState | null>(null);");
expect(appSource).toContain("const handleReturnToToolCenter = useCallback((closeChild?: () => void) => {");
expect(appSource).toContain("t('common.back_to_previous')");
expect(appSource).toContain("width={1080}");
expect(appSource).toContain('centered');
expect(appSource).not.toContain('const [isToolsModalOpen');
});
it('keeps the tool center modal height fixed across group switches and scrolls the list area internally', () => {
it('keeps the unified settings modal height fixed across group switches and scrolls the list area internally', () => {
expect(appUtilityStylesSource).toContain('const toolCenterModalContentStyle = useMemo<React.CSSProperties>(() => ({');
expect(appUtilityStylesSource).toContain("height: 'min(820px, calc(100vh - 64px))'");
expect(appUtilityStylesSource).toContain("const toolCenterModalWorkspaceStyle = useMemo<React.CSSProperties>(() => ({");
@@ -104,7 +105,7 @@ describe('tool center menu entries', () => {
expect(appSource).toContain('style={toolCenterNavScrollStyle}');
expect(appSource).toContain('style={toolCenterContentPanelStyle}');
expect(appSource).toContain('style={toolCenterDetailPanelStyle}');
expect(appSource).toContain('style={toolCenterDetailBodyStyle}');
expect(appSource).toContain('style={isActiveToolCenterPane ? toolCenterDetailBodyStyle : settingsCenterDetailBodyStyle}');
expect(appSource).toContain('style={toolCenterScrollableListStyle}');
expect(appUtilityStylesSource).toContain("overflowY: 'auto'");
expect(appSource).toContain("borderTop: index === 0 ? `1px solid ${overlayTheme.divider}` : 'none'");
@@ -112,12 +113,23 @@ describe('tool center menu entries', () => {
});
it('keeps browser-compatible connection transfer and mounted data-root entries available in the web runtime', () => {
const toolGroupsStart = appSource.indexOf('const toolCenterGroups: SettingsCenterNavigationGroup[] = [');
const configGroupStart = appSource.indexOf("key: 'config',", toolGroupsStart);
const configGroupSource = appSource.slice(
configGroupStart,
appSource.indexOf("key: 'workflow',", configGroupStart),
);
expect(toolGroupsStart).toBeGreaterThan(-1);
expect(configGroupStart).toBeGreaterThan(toolGroupsStart);
expect(appSource).toContain("accept=\".gonavi-conn,.json,.xml,.ncx\"");
expect(appSource).toContain('ExportConnectionsPayload');
expect(appSource).toContain('downloadBrowserTextFile');
expect(appSource).toContain("__GONAVI_WEB_RUNTIME__?.buildType === 'web'");
expect(appSource).toContain('if (isWebRuntime) {\n return (');
expect(appSource).toContain('items: group.items,');
expect(configGroupSource).toContain("key: 'data-root'");
expect(configGroupSource).toContain("handleOpenToolCenterPane('config', 'data-root')");
expect(appSource).toContain('...toolCenterGroups,');
});
it('lets the tool center detail header own embedded tool titles', () => {
@@ -127,35 +139,35 @@ describe('tool center menu entries', () => {
appSource.indexOf('};\n\n return (', renderPaneStart),
);
const connectionPackageSource = renderPaneSource.slice(
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'connection-package')"),
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'data-root')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'connection-package')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'data-root')"),
);
const dataRootSource = renderPaneSource.slice(
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'data-root')"),
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'security-update')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'data-root')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'security-update')"),
);
const securityUpdateSource = renderPaneSource.slice(
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'security-update')"),
renderPaneSource.indexOf("activeToolCenterPane.key === 'schema-compare'"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'security-update')"),
renderPaneSource.indexOf("activeSettingsCenterPane.key === 'schema-compare'"),
);
const dataSyncSource = renderPaneSource.slice(
renderPaneSource.indexOf("activeToolCenterPane.key === 'schema-compare'"),
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'drivers')"),
renderPaneSource.indexOf("activeSettingsCenterPane.key === 'schema-compare'"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'drivers')"),
);
const driverSource = renderPaneSource.slice(
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'drivers')"),
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'snippet-settings')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'drivers')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'snippet-settings')"),
);
const snippetSource = renderPaneSource.slice(
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'snippet-settings')"),
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'shortcut-settings')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'snippet-settings')"),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'shortcut-settings')"),
);
const shortcutSource = renderPaneSource.slice(
renderPaneSource.indexOf("if (activeToolCenterPane.key === 'shortcut-settings')"),
renderPaneSource.indexOf('return null;', renderPaneSource.indexOf("if (activeToolCenterPane.key === 'shortcut-settings')")),
renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'shortcut-settings')"),
renderPaneSource.indexOf('return null;', renderPaneSource.indexOf("if (activeSettingsCenterPane.key === 'shortcut-settings')")),
);
expect(appSource).toContain('activeToolCenterPaneItem?.title ?? activeToolCenterGroup.title');
expect(appSource).toContain('activeSettingsCenterPaneItem?.title ?? activeSettingsCenterGroup.title');
expect(connectionPackageSource).toContain('<ConnectionPackagePasswordModal');
expect(connectionPackageSource).not.toContain('renderUtilityModalTitle');
expect(dataRootSource).toContain('title={null}');
@@ -174,14 +186,15 @@ describe('tool center menu entries', () => {
expect(shortcutSource).not.toContain('renderUtilityModalTitle');
});
it('does not render an extra top back button in the tool center detail header', () => {
it('does not render an extra top back button in tool detail headers', () => {
const combinedGroupsIndex = appSource.indexOf('combinedSettingsCenterGroups.map');
const detailHeaderSource = appSource.slice(
appSource.indexOf("{activeToolCenterPane ? ("),
appSource.indexOf('<div style={toolCenterDetailBodyStyle}>', appSource.indexOf("{activeToolCenterPane ? (")),
appSource.indexOf('{activeSettingsCenterPane ? (', combinedGroupsIndex),
appSource.indexOf('<div style={isActiveToolCenterPane ?', combinedGroupsIndex),
);
expect(detailHeaderSource).toContain('activeToolCenterPaneItem?.title ?? activeToolCenterGroup.title');
expect(detailHeaderSource).toContain('activeToolCenterPaneItem?.description ?? activeToolCenterGroup.description');
expect(detailHeaderSource).toContain('activeSettingsCenterPaneItem?.title ?? activeSettingsCenterGroup.title');
expect(detailHeaderSource).toContain('activeSettingsCenterPaneItem?.description ?? activeSettingsCenterGroup.description');
expect(detailHeaderSource).not.toContain('<Button onClick={closeToolCenterPane}>');
expect(detailHeaderSource).not.toContain("{t('common.back_to_previous')}");
});
@@ -207,11 +220,10 @@ describe('tool center menu entries', () => {
expect(appSource).toContain('const handleFocusSidebarSearch = useCallback(');
expect(appSource).toContain('const antdTheme = useMemo(() => ({');
expect(appSource).toContain('theme={antdTheme}');
expect(appSource).toContain('onOpenTools={handleOpenToolsModal}');
expect(appSource).toContain('onOpenSettings={handleOpenSettingsModal}');
expect(appSource).toContain('onToggleLogPanel={handleToggleLogPanel}');
expect(appSource).toContain('onFocusCommandSearch={handleFocusSidebarSearch}');
expect(appSource).not.toContain('onOpenTools={() => setIsToolsModalOpen(true)}');
expect(appSource).not.toContain('onOpenTools=');
expect(appSource).not.toContain('onOpenSettings={() => setIsSettingsModalOpen(true)}');
expect(appSource).not.toContain('onToggleLogPanel={() => setIsLogPanelOpen((prev) => !prev)}');
expect(appSource).not.toContain('sqlLogCount={sqlLogCount}');
@@ -313,10 +325,10 @@ describe('tool center menu entries', () => {
it('keeps connection modal warm-mounted while leaving the other heavyweight modals conditional', () => {
expect(appSource).toContain('const [isConnectionModalMounted, setIsConnectionModalMounted] = useState(false);');
expect(appSource).toContain('{isConnectionModalMounted && (');
expect(appSource).toContain('{isToolsModalOpen && (');
expect(appSource).toContain('{isSettingsModalOpen && (');
expect(appSource).toContain('{isThemeModalOpen && (');
expect(appSource).toContain('{isShortcutModalOpen && (');
expect(appSource).not.toContain('{isToolsModalOpen && (');
expect(appSource).not.toContain('{isShortcutModalOpen && (');
expect(appSource).not.toContain('{isAISettingsOpen && (');
expect(appSource).toContain('{isDriverModalOpen && (');
expect(appSource).toContain('{isSyncModalOpen && (');
@@ -347,10 +359,10 @@ describe('tool center menu entries', () => {
expect(appSource).not.toContain('setPrimaryPasswordVisible(String(config.password || "").trim() !== "")');
});
it('keeps shortcut manager scrolling inside the modal body', () => {
it('keeps shortcut manager scrolling inside the embedded settings pane', () => {
expect(appSource).toContain('centered');
expect(appSource).toContain("height: 'min(760px, calc(100vh - 80px))'");
expect(appSource).toContain("maxHeight: 'calc(100vh - 80px)'");
expect(appSource).toContain("if (activeSettingsCenterPane.key === 'shortcut-settings')");
expect(appSource).toContain('embedded');
expect(appSource).toContain("body: { paddingTop: 8, overflow: 'hidden', flex: 1, minHeight: 0 }");
expect(appSource).toContain('data-gonavi-shortcut-modal-scroll="true"');
expect(appSource).toContain("height: '100%'");
@@ -373,7 +385,7 @@ describe('tool center menu entries', () => {
['toggleAIPanel', 'toggleAIPanel();'],
['toggleLogPanel', 'handleToggleLogPanel();'],
['toggleTheme', 'selectPresetTheme('],
['openShortcutManager', 'setIsShortcutModalOpen(true);'],
['openShortcutManager', "handleOpenToolCenterPane('workspace', 'shortcut-settings');"],
['toggleMacFullscreen', 'handleTitleBarWindowToggle({ allowMacNativeFullscreen: true });'],
['resetWindowZoom', 'handleManualResetWindowZoom();'],
]);

File diff suppressed because it is too large Load Diff

View File

@@ -984,6 +984,17 @@ describe('Sidebar locate toolbar', () => {
expect(source).not.toContain("justifyContent: 'space-between', borderTop: `1px solid ${darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}`, borderBottom: `1px solid ${darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}`, background: darkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.015)' }}>");
});
it('keeps only the unified settings entry in sidebar utility areas', () => {
const sidebarSource = readSourceFile('./Sidebar.tsx');
const railSource = readSourceFile('./sidebar/SidebarConnectionRail.tsx');
expect(sidebarSource).not.toContain('onOpenTools');
expect(sidebarSource).not.toContain('openTools:');
expect(railSource).not.toContain('handlers.openTools');
expect(railSource).not.toContain('data-gonavi-open-tools-action');
expect(railSource).toContain('handlers.openSettings');
});
it('renders the v2 sidebar rail, command search hint, filter tabs and slow-query footer', () => {
const markup = renderSidebarMarkup({ uiVersion: 'v2', onCreateConnection: mocks.noop });
const source = readSidebarSource();
@@ -1042,9 +1053,10 @@ describe('Sidebar locate toolbar', () => {
expect(markup).toContain('data-gonavi-create-connection-action="true"');
expect(markup).toContain('aria-label="AI 助手"');
expect(markup).toContain('data-gonavi-ai-entry-action="true"');
expect(markup).toContain('aria-label="工具"');
expect(markup).toContain('data-gonavi-open-tools-action="true"');
expect(markup).not.toContain('aria-label="工具"');
expect(markup).not.toContain('data-gonavi-open-tools-action="true"');
expect(markup).toContain('aria-label="设置"');
expect(source).not.toContain('handlers.openTools');
expect(source).toContain('export const buildV2RailConnectionGroups = (');
expect(source).toContain("if (menu.kind === 'v2-connection-group') return renderV2ConnectionGroupContextMenu(menu.node);");
expect(source).toContain('openV2ConnectionContextMenu(event, node);');

View File

@@ -103,7 +103,6 @@ import { Tree, message, Dropdown, MenuProps, Input, Button, Form, Popover, Radio
WarningOutlined,
AimOutlined,
MoreOutlined,
ToolOutlined,
SettingOutlined
} from '@ant-design/icons';
import {
@@ -497,7 +496,6 @@ export const buildAllSavedQueriesTreeNode = (
const Sidebar: React.FC<{
onCreateConnection?: () => void;
onEditConnection?: (conn: SavedConnection) => void;
onOpenTools?: () => void;
onOpenSettings?: () => void;
onToggleAI?: () => void;
onToggleLogPanel?: () => void;
@@ -506,7 +504,6 @@ const Sidebar: React.FC<{
}> = React.memo(({
onCreateConnection,
onEditConnection,
onOpenTools,
onOpenSettings,
onToggleAI,
onToggleLogPanel,
@@ -2920,7 +2917,6 @@ const Sidebar: React.FC<{
const v2LocateCurrentTableLabel = t('sidebar.action.locate_current_table');
const v2LocateCurrentTableUnavailableLabel = t('sidebar.message.locate_current_table_unavailable');
const v2AiAssistantLabel = t('app.sidebar.ai_assistant');
const v2ToolsLabel = t('app.sidebar.tools');
const v2SettingsLabel = t('app.sidebar.settings');
const v2ActiveConnectionHeaderLabel = t('sidebar.active_connection.current_host_database');
const v2NoDatabaseSelectedLabel = t('sidebar.active_connection.no_database_selected');
@@ -2969,7 +2965,6 @@ const Sidebar: React.FC<{
locateCurrentTable: v2LocateCurrentTableLabel,
locateCurrentTableUnavailable: v2LocateCurrentTableUnavailableLabel,
aiAssistant: v2AiAssistantLabel,
tools: v2ToolsLabel,
settings: v2SettingsLabel,
},
handlers: {
@@ -2979,7 +2974,6 @@ const Sidebar: React.FC<{
openExternalSqlFile: handleOpenSQLFileFromToolbar,
locateActiveTab: handleLocateActiveTabInSidebar,
toggleAI: onToggleAI ?? (() => {}),
openTools: onOpenTools ?? (() => {}),
openSettings: onOpenSettings ?? (() => {}),
},
canLocateActiveTab,

View File

@@ -15,7 +15,7 @@ describe('SQL audit workbench wiring', () => {
expect(workbenchSource).toContain('<SqlAuditWorkbench tab={tab} isActive={isActive} />');
});
it('provides both the V2 footer shortcut and the cross-version tool-center entry', () => {
it('provides both the V2 footer shortcut and the cross-version settings-center entry', () => {
const sidebarSource = read('../Sidebar.tsx');
const railSource = read('../sidebar/SqlAuditRailButton.tsx');
const appSource = read('../../App.tsx');
@@ -24,7 +24,7 @@ describe('SQL audit workbench wiring', () => {
expect(railSource).toContain('buildSqlAuditWorkbenchTab()');
expect(appSource).toContain("key: 'sql-audit'");
expect(appSource).toContain('addTab(buildSqlAuditWorkbenchTab())');
expect(appSource).toContain('setIsToolsModalOpen(false)');
expect(appSource).toContain('handleCancelSettingsCenterPane();');
});
it('registers audit labels for docked and detached tab presentations', () => {

View File

@@ -7,7 +7,6 @@ import {
FileAddOutlined,
AimOutlined,
RobotOutlined,
ToolOutlined,
SettingOutlined,
} from '@ant-design/icons';
@@ -31,7 +30,6 @@ export interface SidebarConnectionRailProps {
locateCurrentTable: string;
locateCurrentTableUnavailable: string;
aiAssistant: string;
tools: string;
settings: string;
};
handlers: {
@@ -41,7 +39,6 @@ export interface SidebarConnectionRailProps {
openExternalSqlFile: () => void;
locateActiveTab: () => void;
toggleAI: () => void;
openTools: () => void;
openSettings: () => void;
};
canLocateActiveTab: boolean;
@@ -121,17 +118,6 @@ const SidebarConnectionRail: React.FC<SidebarConnectionRailProps> = ({ labels, h
<RobotOutlined />
</button>
</Tooltip>
<Tooltip title={labels.tools} placement="right">
<button
type="button"
className="gn-v2-rail-tool"
onClick={handlers.openTools}
aria-label={labels.tools}
data-gonavi-open-tools-action="true"
>
<ToolOutlined />
</button>
</Tooltip>
<Tooltip title={labels.settings} placement="right">
<button type="button" className="gn-v2-rail-tool" onClick={handlers.openSettings} aria-label={labels.settings}>
<SettingOutlined />

View File

@@ -753,10 +753,10 @@ describe("i18n catalog", () => {
it("keeps App tools, data root, and about shell copy out of source literals", () => {
const source = readAppSource();
const toolsModalSource = sliceBetween(
const settingsToolsSource = sliceBetween(
source,
"{isToolsModalOpen && (",
"{isSettingsModalOpen && (",
"{isSettingsModalOpen && (() => {",
"{isDataRootModalOpen && (",
);
const dataRootModalSource = sliceBetween(
source,
@@ -769,12 +769,12 @@ describe("i18n catalog", () => {
"{isThemeModalOpen && (",
);
expect(toolsModalSource).not.toContain("工具中心");
expect(toolsModalSource).not.toContain("导入连接配置");
expect(toolsModalSource).not.toContain("导出连接配置");
expect(toolsModalSource).not.toContain("数据同步");
expect(toolsModalSource).not.toContain("驱动管理");
expect(toolsModalSource).not.toContain("数据目录");
expect(settingsToolsSource).not.toContain("工具中心");
expect(settingsToolsSource).not.toContain("导入连接配置");
expect(settingsToolsSource).not.toContain("导出连接配置");
expect(settingsToolsSource).not.toContain("数据同步");
expect(settingsToolsSource).not.toContain("驱动管理");
expect(settingsToolsSource).not.toContain("数据目录");
expect(dataRootModalSource).not.toContain("数据存储位置");
expect(dataRootModalSource).not.toContain("数据目录");
@@ -815,10 +815,10 @@ describe("i18n catalog", () => {
"<AIPanelErrorBoundary",
"<AIChatPanel",
);
const toolsModalSource = sliceBetween(
const settingsToolsSource = sliceBetween(
source,
"{isToolsModalOpen && (",
"{isSettingsModalOpen && (",
"{isSettingsModalOpen && (() => {",
"{isDataRootModalOpen && (",
);
expect(securityInitialStageSource).toContain("app.security_update.stage.checking_saved_config");
@@ -857,7 +857,7 @@ describe("i18n catalog", () => {
expect(dataRootFlowSource).not.toContain("数据目录已更新");
expect(dataRootFlowSource).not.toContain("打开数据目录失败");
expect(sidebarUtilitySource).toContain("app.sidebar.tools");
expect(sidebarUtilitySource).not.toContain("app.sidebar.tools");
expect(sidebarUtilitySource).toContain("app.sidebar.settings");
expect(sidebarUtilitySource).toContain("app.sidebar.ai_assistant");
expect(source).toContain("app.sidebar.resize_width");
@@ -890,16 +890,16 @@ describe("i18n catalog", () => {
expect(aiPanelSource).not.toContain("关闭面板");
expect(aiPanelSource).not.toContain("重新加载");
expect(toolsModalSource).toContain("app.tools.entry.snippets.title");
expect(toolsModalSource).toContain("app.tools.entry.snippets.description");
expect(toolsModalSource).toContain("app.tools.entry.security_update.title");
expect(toolsModalSource).toContain("app.tools.entry.security_update.status_description");
expect(toolsModalSource).toContain("app.tools.entry.security_update.description");
expect(toolsModalSource).not.toContain("代码片段管理");
expect(toolsModalSource).not.toContain("管理 SQL 代码片段和前缀补全。");
expect(toolsModalSource).not.toContain("安全更新");
expect(toolsModalSource).not.toContain("当前状态:");
expect(toolsModalSource).not.toContain("查看已保存配置的安全更新状态。");
expect(settingsToolsSource).toContain("app.tools.entry.snippets.title");
expect(settingsToolsSource).toContain("app.tools.entry.snippets.description");
expect(settingsToolsSource).toContain("app.tools.entry.security_update.title");
expect(settingsToolsSource).toContain("app.tools.entry.security_update.status_description");
expect(settingsToolsSource).toContain("app.tools.entry.security_update.description");
expect(settingsToolsSource).not.toContain("代码片段管理");
expect(settingsToolsSource).not.toContain("管理 SQL 代码片段和前缀补全。");
expect(settingsToolsSource).not.toContain("安全更新");
expect(settingsToolsSource).not.toContain("当前状态:");
expect(settingsToolsSource).not.toContain("查看已保存配置的安全更新状态。");
});
it("keeps App theme modal shell copy out of source literals", () => {
@@ -907,7 +907,7 @@ describe("i18n catalog", () => {
const themeModalSource = sliceBetween(
source,
"{isThemeModalOpen && (",
"{isShortcutModalOpen && (",
"{isProxyModalOpen && (",
);
expect(themeModalSource).not.toContain("主题设置");
@@ -922,15 +922,15 @@ describe("i18n catalog", () => {
it("keeps App shortcut modal shell copy out of source literals", () => {
const source = readAppSource();
const toolsModalSource = sliceBetween(
const settingsToolsSource = sliceBetween(
source,
"{isToolsModalOpen && (",
"{isSettingsModalOpen && (",
"{isSettingsModalOpen && (() => {",
"{isDataRootModalOpen && (",
);
const shortcutModalSource = sliceBetween(
source,
"{isShortcutModalOpen && (",
"{isSnippetModalOpen && (",
"if (activeSettingsCenterPane.key === 'shortcut-settings') {",
"return null;\n };",
);
const shortcutCaptureSource = sliceBetween(
source,
@@ -938,7 +938,7 @@ describe("i18n catalog", () => {
"window.addEventListener('keydown', handleShortcutCapture, true);",
);
expect(toolsModalSource).not.toContain("查看并调整全局快捷键绑定。");
expect(settingsToolsSource).not.toContain("查看并调整全局快捷键绑定。");
expect(shortcutModalSource).not.toContain("统一查看、录制与启停常用快捷键");
expect(shortcutModalSource).not.toContain("已恢复默认快捷键");
expect(shortcutModalSource).not.toContain("请按下快捷键...");
@@ -956,7 +956,7 @@ describe("i18n catalog", () => {
const connectionPackageFlowSource = sliceBetween(
source,
"const importConnectionsPayload = useCallback(async (raw: string, password: string) => {",
"const [isToolsModalOpen, setIsToolsModalOpen] = useState(false);",
"const [toolCenterBackGroupKey, setToolCenterBackGroupKey]",
);
const connectionPackageModalPropsSource = sliceBetween(
source,

View File

@@ -7,8 +7,8 @@ import {
} from './aiEntryLayout';
describe('ai entry layout', () => {
it('keeps legacy sidebar utility buttons limited to tools and settings', () => {
expect(SIDEBAR_UTILITY_ITEM_KEYS).toEqual(['tools', 'settings']);
it('keeps the legacy sidebar utility area limited to the unified settings entry', () => {
expect(SIDEBAR_UTILITY_ITEM_KEYS).toEqual(['settings']);
});
it('attaches the legacy closed AI handle to the content shell', () => {

View File

@@ -1,6 +1,6 @@
import type { CSSProperties } from 'react';
export const SIDEBAR_UTILITY_ITEM_KEYS = ['tools', 'settings'] as const;
export const SIDEBAR_UTILITY_ITEM_KEYS = ['settings'] as const;
export type AIEntryPlacement = 'content-edge';
export type LegacyAIEdgeHandleAttachment = 'content-shell' | 'panel-shell';