import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import TableDesignerSqlPreview, { resolveSqlChangeHighlights } from './TableDesignerSqlPreview'; const mockMonaco = { Range: class { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number; constructor( startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, ) { this.startLineNumber = startLineNumber; this.startColumn = startColumn; this.endLineNumber = endLineNumber; this.endColumn = endColumn; } }, editor: { defineTheme: vi.fn(), }, }; const mockEditor = { deltaDecorations: vi.fn(() => ['decoration-1']), getModel: vi.fn(() => ({ getLineCount: () => 5, getLineMaxColumn: (lineNumber: number) => (lineNumber === 1 ? 22 : 80), })), }; vi.mock('@monaco-editor/react', () => ({ default: ({ beforeMount, defaultLanguage, language, onMount, options, theme, value, }: { beforeMount?: (monaco: any) => void; defaultLanguage?: string; language?: string; onMount?: (editor: any, monaco: any) => void; options?: Record; theme?: string; value?: string; }) => { beforeMount?.(mockMonaco); onMount?.(mockEditor, mockMonaco); return (
{value}
); }, })); describe('TableDesignerSqlPreview', () => { beforeEach(() => { mockEditor.deltaDecorations.mockClear(); mockMonaco.editor.defineTheme.mockClear(); }); it('renders SQL changes in a read-only Monaco SQL editor with explicit syntax highlight theme', () => { const markup = renderToStaticMarkup( , ); 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('"readOnly":true'); expect(markup).toContain('"lineNumbers":"on"'); expect(markup).not.toContain('"glyphMargin":true'); 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('detects only SQL change operation lines instead of highlighting the whole SQL block', () => { const highlights = resolveSqlChangeHighlights([ 'ALTER TABLE "users"', 'ADD COLUMN "age" int NULL;', 'ALTER TABLE "users"', 'RENAME COLUMN "name" TO "display_name";', '-- DuckDB 不支持通过 COMMENT ON COLUMN 持久化字段备注', ].join('\n')); expect(highlights).toEqual([ expect.objectContaining({ kind: 'add', lineNumber: 2 }), expect.objectContaining({ kind: 'rename', lineNumber: 4 }), ]); }); it('adds Monaco decorations to changed SQL lines only', () => { renderToStaticMarkup( , ); expect(mockEditor.deltaDecorations).toHaveBeenCalledWith( [], expect.arrayContaining([ expect.objectContaining({ range: expect.objectContaining({ startLineNumber: 2, endLineNumber: 2 }), options: expect.objectContaining({ className: expect.stringContaining('gonavi-sql-preview-change-line-add'), isWholeLine: true, linesDecorationsClassName: expect.stringContaining('gonavi-sql-preview-change-marker-add'), }), }), expect.objectContaining({ range: expect.objectContaining({ startLineNumber: 4, endLineNumber: 4 }), options: expect.objectContaining({ className: expect.stringContaining('gonavi-sql-preview-change-line-drop'), isWholeLine: true, linesDecorationsClassName: expect.stringContaining('gonavi-sql-preview-change-marker-drop'), }), }), ]), ); const firstDecorationCall = mockEditor.deltaDecorations.mock.calls[0] as unknown as [unknown, unknown[]]; expect(firstDecorationCall[1]).toHaveLength(2); expect(firstDecorationCall[1]).toEqual( expect.arrayContaining([ expect.objectContaining({ options: expect.not.objectContaining({ glyphMarginClassName: expect.any(String), }), }), ]), ); }); it('uses the dark SQL preview theme when dark mode is enabled', () => { const markup = renderToStaticMarkup( , ); 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, }), ); }); });