mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-13 01:54:20 +08:00
✨ feat(settings): 将工具中心整合至设置中心
- 侧栏移除独立工具入口并统一使用设置齿轮 - 设置中心扩展为六组导航并复用原有工具内页 - 统一安全更新、连接配置、AI 设置与工具页面的返回路径 - 清理快捷键捕获、连接密码弹窗和 AI 修复上下文等瞬态状态 - 更新设置、侧栏、SQL 审计与国际化回归测试
This commit is contained in:
@@ -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'");
|
||||
|
||||
@@ -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
@@ -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);');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user