feat(table-designer): 高亮显示 SQL 变更预览

- SQL 变更弹窗接入只读 SQL 高亮预览组件

- 注册明暗主题下的 SQL token 颜色

- 补充 SQL 预览高亮回归测试
This commit is contained in:
Syngnat
2026-04-26 17:38:42 +08:00
parent 5f6acc25da
commit 3a0c5201a0
3 changed files with 185 additions and 19 deletions

View File

@@ -10,6 +10,7 @@ import { useStore } from '../store';
import { DBGetColumns, DBGetIndexes, DBQuery, DBGetForeignKeys, DBGetTriggers, DBShowCreateTable } from '../../wailsjs/go/app/App';
import { hasIndexFormChanged, normalizeIndexFormFromRow, shouldRestoreOriginalIndex, toggleIndexSelection as getNextIndexSelection, type IndexDisplaySnapshot } from './tableDesignerIndexUtils';
import { buildAlterTablePreviewSql, buildCreateTablePreviewSql, hasAlterTableDraftChanges } from './tableDesignerSchemaSql';
import TableDesignerSqlPreview from './TableDesignerSqlPreview';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import { noAutoCapInputProps } from '../utils/inputAutoCap';
import {
@@ -3002,25 +3003,7 @@ END;`;
okText="执行"
cancelText="取消"
>
<div style={{ maxHeight: '400px', overflow: 'hidden', borderRadius: 8, border: darkMode ? '1px solid #333' : '1px solid #eee' }}>
<Editor
height="360px"
defaultLanguage="sql"
language="sql"
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={previewSql}
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 13,
lineNumbers: 'on',
scrollBeyondLastLine: false,
wordWrap: 'on',
automaticLayout: true,
padding: { top: 8, bottom: 8 },
}}
/>
</div>
<TableDesignerSqlPreview sql={previewSql} darkMode={darkMode} />
<p style={{ marginTop: 10, color: '#faad14' }}> SQL</p>
</Modal>

View File

@@ -0,0 +1,95 @@
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import TableDesignerSqlPreview from './TableDesignerSqlPreview';
const mockMonaco = {
editor: {
defineTheme: vi.fn(),
},
};
vi.mock('@monaco-editor/react', () => ({
default: ({
beforeMount,
defaultLanguage,
language,
options,
theme,
value,
}: {
beforeMount?: (monaco: any) => void;
defaultLanguage?: string;
language?: string;
options?: Record<string, any>;
theme?: string;
value?: string;
}) => {
beforeMount?.(mockMonaco);
return (
<div
data-default-language={defaultLanguage}
data-language={language}
data-monaco-editor-mock="true"
data-options={JSON.stringify(options)}
data-theme={theme}
>
{value}
</div>
);
},
}));
describe('TableDesignerSqlPreview', () => {
beforeEach(() => {
mockMonaco.editor.defineTheme.mockClear();
});
it('renders SQL changes in a read-only Monaco SQL editor with explicit syntax highlight theme', () => {
const markup = renderToStaticMarkup(
<TableDesignerSqlPreview
sql={'ALTER TABLE "users"\nRENAME COLUMN "name" TO "display_name";'}
darkMode={false}
/>,
);
expect(markup).toContain('data-table-designer-sql-preview="true"');
expect(markup).toContain('data-monaco-editor-mock="true"');
expect(markup).toContain('data-default-language="sql"');
expect(markup).toContain('data-language="sql"');
expect(markup).toContain('data-theme="gonavi-sql-preview-light"');
expect(markup).toContain('&quot;readOnly&quot;:true');
expect(markup).toContain('&quot;lineNumbers&quot;:&quot;on&quot;');
expect(markup).toContain('ALTER TABLE');
expect(markup).toContain('RENAME COLUMN');
expect(mockMonaco.editor.defineTheme).toHaveBeenCalledWith(
'gonavi-sql-preview-light',
expect.objectContaining({
base: 'vs',
inherit: true,
rules: expect.arrayContaining([
expect.objectContaining({ token: 'keyword', foreground: expect.any(String) }),
expect.objectContaining({ token: 'string', foreground: expect.any(String) }),
expect.objectContaining({ token: 'comment', foreground: expect.any(String) }),
]),
}),
);
});
it('uses the dark SQL preview theme when dark mode is enabled', () => {
const markup = renderToStaticMarkup(
<TableDesignerSqlPreview sql="CREATE TABLE users (id int);" darkMode />,
);
expect(markup).toContain('data-theme="gonavi-sql-preview-dark"');
expect(mockMonaco.editor.defineTheme).toHaveBeenCalledWith(
'gonavi-sql-preview-dark',
expect.objectContaining({
base: 'vs-dark',
inherit: true,
}),
);
});
});

View File

@@ -0,0 +1,88 @@
import Editor, { type BeforeMount } from '@monaco-editor/react';
interface TableDesignerSqlPreviewProps {
sql: string;
darkMode?: boolean;
height?: string | number;
}
const SQL_PREVIEW_LIGHT_THEME = 'gonavi-sql-preview-light';
const SQL_PREVIEW_DARK_THEME = 'gonavi-sql-preview-dark';
const registerSqlPreviewThemes: BeforeMount = (monaco) => {
monaco.editor.defineTheme(SQL_PREVIEW_LIGHT_THEME, {
base: 'vs',
inherit: true,
rules: [
{ token: 'keyword', foreground: '006C9C', fontStyle: 'bold' },
{ token: 'operator', foreground: '8250DF' },
{ token: 'number', foreground: 'B45309' },
{ token: 'string', foreground: '15803D' },
{ token: 'comment', foreground: '64748B', fontStyle: 'italic' },
{ token: 'predefined', foreground: '0F766E' },
],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#0F172A0A',
'editorGutter.background': '#00000000',
'editorLineNumber.foreground': '#94A3B8',
},
});
monaco.editor.defineTheme(SQL_PREVIEW_DARK_THEME, {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'keyword', foreground: '7DD3FC', fontStyle: 'bold' },
{ token: 'operator', foreground: 'C4B5FD' },
{ token: 'number', foreground: 'FDBA74' },
{ token: 'string', foreground: '86EFAC' },
{ token: 'comment', foreground: '94A3B8', fontStyle: 'italic' },
{ token: 'predefined', foreground: '5EEAD4' },
],
colors: {
'editor.background': '#00000000',
'editor.lineHighlightBackground': '#FFFFFF12',
'editorGutter.background': '#00000000',
'editorLineNumber.foreground': '#64748B',
},
});
};
const TableDesignerSqlPreview: React.FC<TableDesignerSqlPreviewProps> = ({
sql,
darkMode = false,
height = '360px',
}) => (
<div
data-table-designer-sql-preview="true"
style={{
maxHeight: 400,
overflow: 'hidden',
borderRadius: 8,
border: darkMode ? '1px solid #333' : '1px solid #eee',
}}
>
<Editor
beforeMount={registerSqlPreviewThemes}
defaultLanguage="sql"
height={height}
language="sql"
options={{
automaticLayout: true,
fontFamily: '"JetBrains Mono", "Cascadia Code", Consolas, monospace',
fontSize: 13,
lineNumbers: 'on',
minimap: { enabled: false },
padding: { top: 8, bottom: 8 },
readOnly: true,
scrollBeyondLastLine: false,
wordWrap: 'on',
}}
theme={darkMode ? SQL_PREVIEW_DARK_THEME : SQL_PREVIEW_LIGHT_THEME}
value={sql}
/>
</div>
);
export default TableDesignerSqlPreview;