From e135956ebadcaba8d21efd3643a40e45d42570b0 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 1 Jul 2026 09:17:54 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(definition-viewer):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AF=B9=E8=B1=A1=E5=AE=9A=E4=B9=89=E9=A1=B5?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E8=A1=8C=E5=8F=B7=E9=87=8D=E5=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 DefinitionViewer 只读 Monaco 实例上显式关闭 stickyScroll,避免滚动后出现悬浮代码行与行号叠层 - 同步关闭 TriggerViewer 的 stickyScroll,统一对象定义类只读查看器滚动表现 - 新增对象定义查看器 Monaco 选项回归测试,锁定只读定义页必须禁用 stickyScroll --- frontend/src/components/DefinitionViewer.tsx | 1 + ...ctDefinitionViewer.monaco-options.test.tsx | 143 ++++++++++++++++++ frontend/src/components/TriggerViewer.tsx | 1 + 3 files changed, 145 insertions(+) create mode 100644 frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx diff --git a/frontend/src/components/DefinitionViewer.tsx b/frontend/src/components/DefinitionViewer.tsx index d1d63570..d7cb6137 100644 --- a/frontend/src/components/DefinitionViewer.tsx +++ b/frontend/src/components/DefinitionViewer.tsx @@ -1019,6 +1019,7 @@ const DefinitionViewer: React.FC = ({ tab }) => { lineHeight: 24, lineNumbers: 'on', lineNumbersMinChars: 4, + stickyScroll: { enabled: false }, scrollBeyondLastLine: false, wordWrap: 'on', automaticLayout: true, diff --git a/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx b/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx new file mode 100644 index 00000000..6de7d15f --- /dev/null +++ b/frontend/src/components/ObjectDefinitionViewer.monaco-options.test.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { act, create } from 'react-test-renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { TabData } from '../types'; +import { I18nProvider } from '../i18n/provider'; +import DefinitionViewer from './DefinitionViewer'; +import TriggerViewer from './TriggerViewer'; + +const storeState = vi.hoisted(() => ({ + connections: [ + { + id: 'conn-1', + name: 'local', + config: { + type: 'postgres', + host: '127.0.0.1', + port: 5432, + user: 'postgres', + password: '', + database: 'main', + }, + }, + ], + theme: 'light', + addTab: vi.fn(), + setActiveContext: vi.fn(), +})); + +const backendApp = vi.hoisted(() => ({ + DBQuery: vi.fn(), +})); + +vi.mock('../store', () => ({ + useStore: (selector: (state: typeof storeState) => unknown) => selector(storeState), +})); + +vi.mock('../../wailsjs/go/app/App', () => backendApp); +vi.mock('../i18n/runtime', () => ({ + syncLanguageRuntime: vi.fn(async () => undefined), +})); + +vi.mock('@ant-design/icons', () => ({ + EditOutlined: () => , +})); + +vi.mock('./MonacoEditor', () => ({ + default: ({ value, options }: any) => ( +
+      {value}
+    
+ ), +})); + +vi.mock('antd', () => ({ + Spin: ({ tip }: any) =>
{tip}
, + Alert: ({ message, description }: any) =>
{message}{description}
, + Button: ({ children, onClick, icon }: any) => ( + + ), +})); + +const flushPromises = async (count = 6) => { + for (let i = 0; i < count; i += 1) { + await Promise.resolve(); + } +}; + +const renderWithI18n = (node: React.ReactElement) => ( + undefined} + > + {node} + +); + +const routineTab: TabData = { + id: 'routine-def-conn-1-main-reporting.refresh_stats', + title: '函数: reporting.refresh_stats', + type: 'routine-def', + connectionId: 'conn-1', + dbName: 'main', + routineName: 'reporting.refresh_stats', + routineType: 'FUNCTION', + schemaName: 'reporting', +}; + +const triggerTab: TabData = { + id: 'trigger-conn-1-main-audit.users_bi', + title: '触发器: audit.users_bi', + type: 'trigger', + connectionId: 'conn-1', + dbName: 'main', + triggerName: 'audit.users_bi', + triggerTableName: 'audit.users', + schemaName: 'audit', +}; + +describe('Object definition viewers Monaco options', () => { + beforeEach(() => { + backendApp.DBQuery.mockReset(); + backendApp.DBQuery + .mockResolvedValueOnce({ + success: true, + data: [{ routine_definition: 'CREATE OR REPLACE FUNCTION reporting.refresh_stats() RETURNS void AS $$ BEGIN END; $$ LANGUAGE plpgsql;' }], + }) + .mockResolvedValueOnce({ + success: true, + data: [{ trigger_definition: 'CREATE TRIGGER users_bi BEFORE INSERT ON audit.users EXECUTE FUNCTION audit.audit_users();' }], + }); + }); + + it('disables sticky scroll for read-only routine definitions', async () => { + let renderer: any; + await act(async () => { + renderer = create(renderWithI18n()); + await flushPromises(); + }); + + const editor = renderer.root.findByProps({ 'data-editor': 'true' }); + expect(editor.props['data-sticky-scroll-enabled']).toBe('false'); + }); + + it('disables sticky scroll for read-only trigger definitions', async () => { + let renderer: any; + await act(async () => { + renderer = create(renderWithI18n()); + await flushPromises(); + }); + + const editor = renderer.root.findByProps({ 'data-editor': 'true' }); + expect(editor.props['data-sticky-scroll-enabled']).toBe('false'); + }); +}); diff --git a/frontend/src/components/TriggerViewer.tsx b/frontend/src/components/TriggerViewer.tsx index e1ac9a18..e48e582b 100644 --- a/frontend/src/components/TriggerViewer.tsx +++ b/frontend/src/components/TriggerViewer.tsx @@ -589,6 +589,7 @@ LIMIT 1`]; lineHeight: 24, lineNumbers: 'on', lineNumbersMinChars: 4, + stickyScroll: { enabled: false }, scrollBeyondLastLine: false, wordWrap: 'on', automaticLayout: true,