🐛 fix(query-editor): 统一对象查看与修改字号

- object-edit 查询态补齐与查看页一致的 Monaco 字号和行高配置
- 统一对象查看与修改的行号宽度,避免视觉尺寸不一致
- 补充 QueryEditor 与 DefinitionViewer Monaco 选项回归测试
This commit is contained in:
Syngnat
2026-07-02 10:47:08 +08:00
parent b220dd051f
commit 92eb4266da
3 changed files with 81 additions and 24 deletions

View File

@@ -50,6 +50,9 @@ vi.mock('./MonacoEditor', () => ({
data-editor="true"
data-readonly={String(options?.readOnly)}
data-sticky-scroll-enabled={String(options?.stickyScroll?.enabled)}
data-font-size={String(options?.fontSize)}
data-line-height={String(options?.lineHeight)}
data-line-numbers-min-chars={String(options?.lineNumbersMinChars)}
>
{value}
</pre>
@@ -128,6 +131,9 @@ describe('Object definition viewers Monaco options', () => {
const editor = renderer.root.findByProps({ 'data-editor': 'true' });
expect(editor.props['data-sticky-scroll-enabled']).toBe('false');
expect(editor.props['data-font-size']).toBe('14');
expect(editor.props['data-line-height']).toBe('24');
expect(editor.props['data-line-numbers-min-chars']).toBe('4');
});
it('disables sticky scroll for read-only trigger definitions', async () => {

View File

@@ -151,6 +151,7 @@ const editorState = vi.hoisted(() => {
decorationIds: [] as string[],
contentHoverCalls: [] as any[],
latestOnChange: null as null | ((value?: string) => void),
latestOptions: null as any,
};
const offsetAt = (position: { lineNumber: number; column: number }) => {
const text = state.value;
@@ -295,10 +296,11 @@ vi.mock('../utils/autoFetchVisibility', () => ({
}));
vi.mock('@monaco-editor/react', () => ({
default: ({ defaultValue, onChange, onMount }: any) => {
default: ({ defaultValue, onChange, onMount, options }: any) => {
React.useEffect(() => {
editorState.value = String(defaultValue || '');
editorState.latestOnChange = onChange;
editorState.latestOptions = options ?? null;
onMount?.(editorState.editor, {
editor: { setTheme: vi.fn() },
KeyMod: { CtrlCmd: 2048, WinCtrl: 256, Alt: 512, Shift: 1024 },
@@ -706,6 +708,7 @@ describe('QueryEditor external SQL save', () => {
editorState.decorationIds = [];
editorState.contentHoverCalls = [];
editorState.latestOnChange = null;
editorState.latestOptions = null;
editorState.editor.getValue.mockClear();
editorState.editor.getModel().getValue.mockClear();
editorState.editor.getModel().getValueLength.mockClear();
@@ -840,6 +843,48 @@ describe('QueryEditor external SQL save', () => {
renderer?.unmount();
});
it('disables sticky scroll for object-edit query tabs', async () => {
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.database = 'ORCLPDB1';
const plsql = [
'CREATE OR REPLACE PROCEDURE cproc_demo AS',
'BEGIN',
' NULL;',
'END cproc_demo;',
'/;',
].join('\n');
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ dbName: 'ORCLPDB1', query: plsql, queryMode: 'object-edit' })} />);
});
expect(editorState.latestOptions?.stickyScroll?.enabled).toBe(false);
expect(editorState.latestOptions?.fontSize).toBe(14);
expect(editorState.latestOptions?.lineHeight).toBe(24);
expect(editorState.latestOptions?.lineNumbersMinChars).toBe(4);
expect(editorState.editor.updateOptions).toHaveBeenCalledWith(expect.objectContaining({
fontSize: 14,
lineHeight: 24,
lineNumbersMinChars: 4,
stickyScroll: { enabled: false },
}));
renderer?.unmount();
});
it('keeps standard query tabs on the default sticky scroll behavior', async () => {
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab()} />);
});
expect(editorState.latestOptions?.stickyScroll).toBeUndefined();
expect(editorState.latestOptions?.fontSize).toBeUndefined();
expect(editorState.latestOptions?.lineHeight).toBeUndefined();
expect(editorState.editor.updateOptions.mock.calls[0]?.[0]?.stickyScroll).toBeUndefined();
renderer?.unmount();
});
it('runs the preceding Oracle procedure when the cursor is on the SQLPlus slash delimiter', async () => {
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.database = 'ORCLPDB1';

View File

@@ -154,6 +154,29 @@ const QUERY_EDITOR_MONACO_FIND_OPTIONS = {
} as const;
const QUERY_EDITOR_NATIVE_SELECT_CURRENT_LINE_EVENT = 'gonavi:native-select-current-line';
const buildQueryEditorMonacoOptions = (isObjectEditQueryTab: boolean) => ({
minimap: { enabled: false },
automaticLayout: true,
fixedOverflowWidgets: true,
find: QUERY_EDITOR_MONACO_FIND_OPTIONS,
hover: {
enabled: true,
delay: QUERY_EDITOR_HOVER_DELAY_MS,
above: false,
},
scrollBeyondLastLine: false,
quickSuggestions: { other: true, comments: false, strings: false },
suggestOnTriggerCharacters: true,
...(isObjectEditQueryTab
? {
fontSize: 14,
lineHeight: 24,
lineNumbersMinChars: 4,
stickyScroll: { enabled: false },
}
: {}),
});
const QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER = '{SQL}';
const escapeQueryEditorObjectEditSqlLiteral = (value: unknown): string => (
@@ -815,6 +838,10 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
));
const isExternalSQLFileTab = Boolean(String(tab.filePath || '').trim());
const isObjectEditQueryTab = tab.type === 'query' && tab.queryMode === 'object-edit';
const queryEditorMonacoOptions = useMemo(
() => buildQueryEditorMonacoOptions(isObjectEditQueryTab),
[isObjectEditQueryTab],
);
type ResultSet = QueryEditorResultSet;
@@ -2616,15 +2643,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
}
lastEditorCursorPositionRef.current = normalizeEditorPosition(editor.getPosition?.());
editor.updateOptions?.({
fixedOverflowWidgets: true,
find: QUERY_EDITOR_MONACO_FIND_OPTIONS,
hover: {
enabled: true,
delay: QUERY_EDITOR_HOVER_DELAY_MS,
above: false,
},
});
editor.updateOptions?.(buildQueryEditorMonacoOptions(isObjectEditQueryTab));
const applyNavigationHoverStateAtPosition = (targetPosition: { lineNumber: number; column: number } | null) => {
if (!ctrlMetaPressedRef.current) {
@@ -6258,20 +6277,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
syncQueryDraft(nextValue);
}}
onMount={handleEditorDidMount}
options={{
minimap: { enabled: false },
automaticLayout: true,
fixedOverflowWidgets: true,
find: QUERY_EDITOR_MONACO_FIND_OPTIONS,
hover: {
enabled: true,
delay: QUERY_EDITOR_HOVER_DELAY_MS,
above: false,
},
scrollBeyondLastLine: false,
quickSuggestions: { other: true, comments: false, strings: false },
suggestOnTriggerCharacters: true,
}}
options={queryEditorMonacoOptions}
/>
</div>
</div>