diff --git a/frontend/src/components/DataGrid.ddl.test.tsx b/frontend/src/components/DataGrid.ddl.test.tsx index 1a7338dd..2a553ed7 100644 --- a/frontend/src/components/DataGrid.ddl.test.tsx +++ b/frontend/src/components/DataGrid.ddl.test.tsx @@ -4134,7 +4134,6 @@ describe('DataGrid DDL interactions', () => { const content = textContent(renderer!.root); expect(content).toContain('SCHEMA DESIGNER'); - expect(content).toContain('字段'); expect(content).toContain('id'); expect(content).toContain('name'); }); @@ -4168,7 +4167,6 @@ describe('DataGrid DDL interactions', () => { const content = textContent(renderer!.root); expect(content).toContain('SCHEMA DESIGNER'); - expect(content).toContain('字段'); expect(content).toContain('id'); expect(content).toContain('name'); }); @@ -4256,7 +4254,7 @@ describe('DataGrid DDL interactions', () => { expect(content).not.toContain('gn-v2-data-grid-fields-view'); expect(content).toContain('数据预览'); expect(content).toContain('结果视图'); - expect(content).toContain('字段信息'); + expect(findButton(renderer!, '字段信息')).toBeTruthy(); }); it('keeps the v2 fields tab as read-only field info for views', async () => { @@ -4285,8 +4283,8 @@ describe('DataGrid DDL interactions', () => { }); await waitForEffects(); - expect(textContent(renderer!.root)).toContain('字段信息'); - expect(textContent(renderer!.root)).not.toContain('对象设计'); + expect(findButton(renderer!, '字段信息')).toBeTruthy(); + expect(findButton(renderer!, '对象设计')).toBeUndefined(); await act(async () => { findButton(renderer!, '字段信息').props.onClick(); diff --git a/frontend/src/components/JVMResourceBrowser.layout.test.tsx b/frontend/src/components/JVMResourceBrowser.layout.test.tsx index e81e40eb..b09ee208 100644 --- a/frontend/src/components/JVMResourceBrowser.layout.test.tsx +++ b/frontend/src/components/JVMResourceBrowser.layout.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; -import { describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { getCurrentLanguage, setCurrentLanguage } from '../i18n'; import JVMResourceBrowser from './JVMResourceBrowser'; vi.mock('@monaco-editor/react', () => ({ @@ -53,6 +54,17 @@ vi.mock('./jvm/JVMChangePreviewModal', () => ({ })); describe('JVMResourceBrowser layout', () => { + let previousLanguage: ReturnType; + + beforeEach(() => { + previousLanguage = getCurrentLanguage(); + setCurrentLanguage('zh-CN'); + }); + + afterEach(() => { + setCurrentLanguage(previousLanguage); + }); + it('renders a dedicated vertical scroll shell for tall snapshot content', () => { const markup = renderToStaticMarkup( { expect(source).toContain('refreshGlobalExternalSQLRootNode(false)'); expect(source).toContain("request.objectGroup === 'externalSqlFiles'"); expect(source).toContain("t('sidebar.message.locate_external_sql_file_not_found', { path: request.filePath })"); - expect(source).toContain('filePath: data.filePath || undefined'); + expect(source).toContain('filePath: data.filePath'); expect(source).toContain("key: 'add-external-sql-directory'"); expect(source).toContain("key: 'new-external-sql-file'"); expect(source).toContain("key: 'rename-external-sql-file'"); @@ -1645,7 +1645,7 @@ describe('Sidebar locate toolbar', () => { it('remounts the v2 tree when object filters change to avoid rc-tree node reuse drift', () => { const source = readSidebarSource(); - expect(source).toContain("key={isV2Ui ? `v2-tree-${v2ExplorerFilter}` : 'legacy-tree'}"); + expect(source).toContain("key={`${isV2Ui ? `v2-tree-${v2ExplorerFilter}` : 'legacy-tree'}-${sidebarObjectVisibilitySignature}`}"); expect(source).toContain('treeData={isV2Ui ? v2VisibleTreeData : displayTreeData}'); }); @@ -2697,15 +2697,17 @@ describe('Sidebar locate toolbar', () => { it('routes v2 database context menu shell copy through i18n wrappers in Sidebar', () => { const source = readSidebarSource(); + const entityModalsSource = readSourceFile('./sidebar/SidebarEntityModals.tsx'); + const externalSqlWorkflowSource = readSourceFile('./sidebar/SidebarExternalSqlWorkflow.tsx'); const objectActionsSource = readSourceFile('./sidebar/useSidebarObjectActions.tsx'); const v2ActionHandlersSource = readSourceFile('./sidebar/useSidebarV2ActionHandlers.tsx'); const createSchemaSource = objectActionsSource.slice( objectActionsSource.indexOf('const openCreateSchemaModal = (node: any) => {'), objectActionsSource.indexOf('const openRenameSchemaModal = (node: any) => {'), ); - const runSqlSource = source.slice( - source.indexOf('const handleRunSQLFile = async (node: any) => {'), - source.indexOf('const handleOpenSQLFileFromToolbar = async () => {'), + const runSqlSource = externalSqlWorkflowSource.slice( + externalSqlWorkflowSource.indexOf('const handleRunSQLFile = async (node: any) => {'), + externalSqlWorkflowSource.indexOf('const handleOpenSQLFileFromToolbar = async () => {'), ); const databaseShellSource = objectActionsSource.slice( objectActionsSource.indexOf('const handleRenameDatabase = async () => {'), @@ -2724,12 +2726,12 @@ describe('Sidebar locate toolbar', () => { expect(createSchemaSource).toContain("message.error(t('sidebar.message.schema_target_missing'))"); expect(createSchemaSource).toContain("message.success(t('sidebar.message.schema_created'))"); expect(createSchemaSource).toContain("message.error(t('sidebar.message.operation_create_failed'"); - expect(source).toContain("t('sidebar.v2_database_menu.new_schema')"); - expect(source).toContain("t('sidebar.field.schema_name')"); - expect(source).toContain("t('sidebar.validation.schema_name_required')"); + expect(entityModalsSource).toContain("t('sidebar.v2_database_menu.new_schema')"); + expect(entityModalsSource).toContain("t('sidebar.field.schema_name')"); + expect(entityModalsSource).toContain("t('sidebar.validation.schema_name_required')"); - expect(runSqlSource).toContain("message.error(t('sidebar.message.connection_config_not_found'))"); - expect(runSqlSource).toContain("data.fileName || t('sidebar.sql_file_exec.title')"); + expect(runSqlSource).toContain("message.warning(t('sidebar.message.select_connection_or_database_first'))"); + expect(runSqlSource).toContain('fileName: data.fileName'); expect(runSqlSource).toContain("message.error(t('sidebar.message.read_file_failed'"); expect(databaseShellSource).toContain("message.error(t('sidebar.message.database_name_required'))"); @@ -2740,9 +2742,9 @@ describe('Sidebar locate toolbar', () => { expect(databaseShellSource).toContain("content: t('sidebar.modal.confirm_delete_database.content', { name: dbName })"); expect(databaseShellSource).toContain("message.success(t('sidebar.message.database_deleted'))"); expect(databaseShellSource).toContain("message.error(t('sidebar.message.operation_drop_failed'"); - expect(source).toContain("t('sidebar.modal.rename_database.title'"); - expect(source).toContain("t('sidebar.field.new_database_name')"); - expect(source).toContain("t('sidebar.validation.new_database_name_required')"); + expect(entityModalsSource).toContain("t('sidebar.modal.rename_database.title'"); + expect(entityModalsSource).toContain("t('sidebar.field.new_database_name')"); + expect(entityModalsSource).toContain("t('sidebar.validation.new_database_name_required')"); expect(databaseActionSource).toContain("message.success(t('sidebar.message.database_closed'))"); @@ -3020,13 +3022,14 @@ describe('Sidebar locate toolbar', () => { it('localizes v2 saved-query and external SQL root shell copy', () => { const source = readSidebarSource(); + const externalSqlWorkflowSource = readSourceFile('./sidebar/SidebarExternalSqlWorkflow.tsx'); const legacyMenuSource = readLegacyNodeMenuSource(); const loadTablesStart = source.indexOf('const loadTables = async (node: any) => {'); const loadTablesEnd = source.indexOf('const config = {', loadTablesStart); const loadTablesSource = source.slice(loadTablesStart, loadTablesEnd); - const externalSqlFlowStart = source.indexOf('const handleAddExternalSQLDirectory = async (node: any) => {'); - const externalSqlFlowEnd = source.indexOf('const cancelSQLFileExecution = () => {', externalSqlFlowStart); - const externalSqlFlowSource = source.slice(externalSqlFlowStart, externalSqlFlowEnd); + const externalSqlFlowStart = externalSqlWorkflowSource.indexOf('const handleAddExternalSQLDirectory = async (node: any) => {'); + const externalSqlFlowEnd = externalSqlWorkflowSource.indexOf('\n return {', externalSqlFlowStart); + const externalSqlFlowSource = externalSqlWorkflowSource.slice(externalSqlFlowStart, externalSqlFlowEnd); const treeTitleSource = readSourceFile('./sidebar/SidebarTreeTitle.tsx'); const treeTitleStart = 0; const treeTitleEnd = treeTitleSource.length; diff --git a/frontend/src/components/SidebarNestedGroupMenu.test.ts b/frontend/src/components/SidebarNestedGroupMenu.test.ts index dc9ac2b6..aca47aad 100644 --- a/frontend/src/components/SidebarNestedGroupMenu.test.ts +++ b/frontend/src/components/SidebarNestedGroupMenu.test.ts @@ -100,6 +100,7 @@ describe('Sidebar nested group menu', () => { editItem.onClick(); expect(createTagForm.setFieldsValue).toHaveBeenLastCalledWith({ name: 'Group 1', + environmentType: 'local', parentTagId: 'root', connectionIds: ['host-1'], }); diff --git a/frontend/src/components/TabManager.hover.test.tsx b/frontend/src/components/TabManager.hover.test.tsx index 89c40799..4961879b 100644 --- a/frontend/src/components/TabManager.hover.test.tsx +++ b/frontend/src/components/TabManager.hover.test.tsx @@ -127,7 +127,7 @@ describe('TabManager hover info', () => { it('memoizes the tab workbench so parent-only modal state does not repaint open tabs', () => { const source = readFileSync(new URL('./TabManager.tsx', import.meta.url), 'utf8'); - expect(source).toContain('const TabManager: React.FC = React.memo(() => {'); + expect(source).toContain('const TabManager: React.FC = React.memo(({ onFocusSidebarSearch }) => {'); }); it('routes the workspace close command through the docked active tab close coordinator', () => { @@ -473,7 +473,8 @@ describe('TabManager hover info', () => { expect(source).not.toContain(text); }); expect(source).toContain('confirmDirtyTabsOrClose();'); - expect(source).toContain("getSQLFileTabDraft(tab.id, String(tab.query ?? ''))"); + expect(source).toContain('const latestTab = useStore.getState().tabs.find((candidate) => candidate.id === tab.id);'); + expect(source).toContain("String(latestTab?.query ?? tab.query ?? '')"); expect(source).toContain('hasSQLFileTabUnsavedChanges({ ...tab, query: draft }, normalizeSQLFileReadContent(res.data))'); expect(source).toContain('WriteSQLFile(filePath, draft)'); expect(source).toContain('clearSQLFileTabDraft(tab.id)'); diff --git a/frontend/src/components/connectionModal/connectionModalConfig.keepalive.test.ts b/frontend/src/components/connectionModal/connectionModalConfig.keepalive.test.ts index 214422c8..33c6b88d 100644 --- a/frontend/src/components/connectionModal/connectionModalConfig.keepalive.test.ts +++ b/frontend/src/components/connectionModal/connectionModalConfig.keepalive.test.ts @@ -142,12 +142,18 @@ describe("connectionModalConfig keepalive", () => { expect(config.keepAliveSQL).toBe(""); }); - it("persists readOnly only for datasource types that support production guard", async () => { + it("persists protection only for datasource types that support production guard", async () => { + const protection = { + restrictDataEdit: true, + restrictStructureEdit: true, + restrictScriptExecution: true, + restrictDataImport: true, + }; const sqlConfig = await buildConnectionConfig({ values: { ...buildBaseValues(), type: "postgres", - readOnly: true, + ...protection, }, forPersist: true, translate, @@ -157,13 +163,15 @@ describe("connectionModalConfig keepalive", () => { ...buildBaseValues(), type: "redis", port: 6379, - readOnly: true, + ...protection, }, forPersist: true, translate, }); + expect(sqlConfig.protection).toEqual(protection); expect(sqlConfig.readOnly).toBe(true); + expect(redisConfig.protection).toBeUndefined(); expect(redisConfig.readOnly).toBe(false); }); });