test(frontend): 同步现有组件行为契约

This commit is contained in:
mango
2026-07-26 00:25:10 +08:00
parent 23cb8097c0
commit aec7e596d6
6 changed files with 50 additions and 27 deletions

View File

@@ -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();

View File

@@ -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<typeof getCurrentLanguage>;
beforeEach(() => {
previousLanguage = getCurrentLanguage();
setCurrentLanguage('zh-CN');
});
afterEach(() => {
setCurrentLanguage(previousLanguage);
});
it('renders a dedicated vertical scroll shell for tall snapshot content', () => {
const markup = renderToStaticMarkup(
<JVMResourceBrowser

View File

@@ -954,7 +954,7 @@ describe('Sidebar locate toolbar', () => {
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;

View File

@@ -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'],
});

View File

@@ -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<TabManagerProps> = React.memo<TabManagerProps>(({ 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)');

View File

@@ -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);
});
});