🐛 fix(query-editor): 统一主题背景与结果区布局

让 Monaco、SQL 工具栏、数据网格和嵌入日志统一使用当前主题的工作台表面色,并兼容自定义主题覆盖。

统一结果 Tab 的高度、圆角与间距,移除嵌入日志重复标题,将清空日志和隐藏结果区操作并排放到 Tab 栏右侧。

补充主题、工具栏、结果 Tab 和日志面板回归测试。
This commit is contained in:
Syngnat
2026-07-30 17:49:58 +08:00
parent 84569e409c
commit ffd1be8ad6
12 changed files with 188 additions and 74 deletions

View File

@@ -714,7 +714,7 @@ const renderDataTableView = () => (
onClearFiltersAndSorts={clearAllFiltersAndSorts}
/>
<div ref={containerRef} style={{ flex: 1, overflow: 'hidden', position: 'relative', minHeight: 0, display: 'flex', flexDirection: 'column', background: bgContent, borderRadius: panelRadius, border: `1px solid ${panelFrameColor}`, boxSizing: 'border-box' }}>
<div ref={containerRef} style={{ flex: 1, overflow: 'hidden', position: 'relative', minHeight: 0, display: 'flex', flexDirection: 'column', background: `var(--gn-bg-panel, ${bgContent})`, borderRadius: panelRadius, border: `1px solid ${panelFrameColor}`, boxSizing: 'border-box' }}>
{contextHolder}
{exportProgressModal}
<DataGridModals

View File

@@ -3,6 +3,7 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
import { act, create, type ReactTestRenderer } from "react-test-renderer";
import { I18nProvider } from "../i18n/provider";
import { readV2ThemeCss } from "../test/readV2ThemeCss";
import LogPanel from "./LogPanel";
const storeState = {
@@ -109,6 +110,8 @@ describe("LogPanel i18n", () => {
beforeEach(() => {
storeState.sqlLogs = [];
storeState.clearSqlLogs.mockClear();
storeState.theme = "light";
storeState.appearance = { enabled: true, opacity: 1, blur: 0, uiVersion: "legacy" };
});
it("renders log panel chrome in the active language", () => {
@@ -198,4 +201,43 @@ describe("LogPanel i18n", () => {
});
expect(onDiagnoseExecutionError).toHaveBeenCalledTimes(1);
});
it("omits duplicate log chrome from the embedded log tab", () => {
storeState.sqlLogs = [{
id: "log-embedded",
timestamp: Date.UTC(2026, 6, 30, 8, 0, 0),
sql: "SELECT 1",
status: "success",
duration: 3,
}];
const renderer = renderLogPanel({ variant: "embedded" });
const renderedText = textContent(renderer.toJSON());
expect(renderedText).not.toContain("SQL execution log");
expect(renderedText).not.toContain("Track execution status, duration, and errors for quick review.");
expect(renderer.root.findAll((node) => node.props?.title === "Clear logs")).toHaveLength(0);
expect(renderedText).toContain("SELECT 1");
});
it("uses the shared SQL workbench background for the embedded log surface", () => {
storeState.appearance = { enabled: true, opacity: 1, blur: 0, uiVersion: "v2" };
const renderer = renderLogPanel({ variant: "embedded" });
const embeddedPanel = renderer.root.findByProps({ className: "log-panel-embedded" });
const scrollPanel = renderer.root.findByProps({ className: "log-panel-scroll" });
const css = readV2ThemeCss();
const embeddedTableCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .log-panel-embedded .log-panel-table .ant-table {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-result-panel-header .query-result-panel-header-title'),
);
expect(embeddedPanel.props.style.background).toBe(
"var(--gn-query-workbench-bg, var(--gn-bg-panel-2))",
);
expect(scrollPanel.props.style.padding).toBe("0 0 12px");
expect(embeddedTableCss).toContain(
"background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;",
);
});
});

View File

@@ -71,7 +71,6 @@ const LogPanel: React.FC<LogPanelProps> = ({
? `rgba(255, 255, 255, ${Math.max(0.28, opacity * 0.48)})`
: `rgba(0, 0, 0, ${Math.max(0.18, opacity * 0.36)})`;
const isEmbedded = variant === 'embedded';
const logCountLabel = sqlLogs.length.toLocaleString();
const columns = [
{
@@ -118,7 +117,7 @@ const LogPanel: React.FC<LogPanelProps> = ({
style={{
flex: 1,
overflow: 'auto',
padding: isEmbedded ? '0 12px 12px' : '8px 10px 10px',
padding: isEmbedded ? '0 0 12px' : '8px 10px 10px',
}}
>
{sqlLogs.length === 0 ? (
@@ -187,60 +186,18 @@ const LogPanel: React.FC<LogPanelProps> = ({
if (isEmbedded) {
return (
<div
className="log-panel-embedded"
style={{
flex: 1,
minHeight: 0,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
background: isV2Ui
? 'var(--gn-query-workbench-bg, var(--gn-bg-panel-2))'
: undefined,
}}
>
<div
style={{
flex: '0 0 auto',
padding: '8px 12px',
borderBottom: `1px solid ${panelDividerColor}`,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: 12,
minHeight: 40,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
<div
style={{
width: 26,
height: 26,
borderRadius: 8,
display: 'grid',
placeItems: 'center',
background: panelAccentSoftBg,
color: panelAccentColor,
flexShrink: 0,
}}
>
<BugOutlined />
</div>
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 12, fontWeight: 600, color: panelPrimaryTextColor }}>
{t('log_panel.description')}
</div>
<div style={{ fontSize: 11, color: panelMutedTextColor }}>
{logCountLabel}
</div>
</div>
</div>
<Tooltip title={t('log_panel.action.clear')}>
<Button
type="text"
size="small"
icon={<ClearOutlined />}
onClick={clearSqlLogs}
style={{ color: panelMutedTextColor }}
/>
</Tooltip>
</div>
{executionError && (
<div style={{ padding: '12px 12px 0' }}>
<div style={{

View File

@@ -25,7 +25,7 @@ const contrastRatio = (foreground: string, background: string): number => {
};
describe('GoNavi Monaco themes', () => {
it('defines bold SQL keyword colors that remain AA-readable across every built-in preset', () => {
it('defines bold SQL keyword colors that remain AA-readable on the shared workbench surface', () => {
const defineTheme = vi.fn();
registerGonaviMonacoThemes({ editor: { defineTheme } } as never);
@@ -56,7 +56,7 @@ describe('GoNavi Monaco themes', () => {
for (const preset of BUILTIN_CUSTOM_THEME_PRESETS) {
const keyword = (preset.baseMode === 'dark' ? darkRules : lightRules)
.find((rule: any) => rule.token === 'keyword.sql');
const background = readHexProperty(preset.css, '--gn-bg-input');
const background = readHexProperty(preset.css, '--gn-bg-panel-2');
expect(
contrastRatio(`#${keyword.foreground}`, background),
`${preset.id} SQL keyword must contrast with its editor background`,

View File

@@ -3487,11 +3487,30 @@ describe('QueryEditor external SQL save', () => {
expect(textContent(renderer!.toJSON())).not.toContain('结果 1 (2)');
});
it('keeps query result tabs compact, centered, and readable in v2 UI', () => {
it('keeps query result tabs flush, full-height, and readable in v2 UI', () => {
const source = readFileSync(new URL('./QueryEditorResultsPanel.tsx', import.meta.url), 'utf8');
const css = readV2ThemeCss();
const resultNavCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-extra-content {'),
);
const resultTabCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-list {'),
);
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab {');
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab-btn {');
expect(resultNavCss).toContain('padding: 0 8px 0 0;');
expect(resultNavCss).toContain('min-height: 46px;');
expect(resultTabCss).toContain('height: 46px !important;');
expect(resultTabCss).toContain('margin: 0 !important;');
expect(resultTabCss).toContain('border-radius: 8px !important;');
expect(css).toContain([
'body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-wrap,',
'body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-list {',
' min-height: 46px;',
'}',
].join('\n'));
expect(css).toContain('user-select: none;');
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tab-text {');
});
@@ -3767,6 +3786,25 @@ describe('QueryEditor external SQL save', () => {
const v2Renderer = renderResultsPanel(true);
expect(v2Renderer.root.findAll((node) => node.props?.['data-log-panel'] === 'true')).toHaveLength(1);
expect(v2Renderer.root.findAll((node) => node.props?.['data-tab-key'] === '__gonavi_sql_execution_log__')).toHaveLength(1);
const tabActions = v2Renderer.root.findByProps({ className: 'query-result-panel-tab-actions' });
const actionButtons = tabActions.findAll((node) => node.type === 'button');
const resultPanelStyles = v2Renderer.root.findAll((node) => node.type === 'style')
.map((node) => textContent(node))
.join('\n');
expect(actionButtons.map((node) => node.props.className)).toEqual([
'query-result-panel-clear query-result-panel-tab-action',
'query-result-panel-hide query-result-panel-tab-action',
]);
expect(resultPanelStyles).toContain(
'.query-result-panel-tab-actions { display: inline-flex; flex-direction: row;',
);
expect(resultPanelStyles).toContain(
'.query-result-tabs .ant-tabs-extra-content .query-result-panel-tab-action { width: 28px; min-width: 28px; height: 28px !important; min-height: 28px !important; padding: 0 !important;',
);
act(() => {
actionButtons[0].props.onClick();
});
expect(storeState.clearSqlLogs).toHaveBeenCalledTimes(1);
v2Renderer.unmount();
const emptyV2Renderer = renderResultsPanel(true, 0);

View File

@@ -1,7 +1,8 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Dropdown, Tabs, Tooltip, message, type MenuProps } from 'antd';
import { BugOutlined, CloseOutlined, CopyOutlined, EyeInvisibleOutlined, PushpinOutlined, RobotOutlined } from '@ant-design/icons';
import { BugOutlined, ClearOutlined, CloseOutlined, CopyOutlined, EyeInvisibleOutlined, PushpinOutlined, RobotOutlined } from '@ant-design/icons';
import { useStore } from '../store';
import type { EditRowLocator } from '../utils/rowLocator';
import type { GridSortInfoItem } from '../utils/dataGridSort';
import type { QueryResultPaginationState } from '../utils/queryResultPagination';
@@ -164,6 +165,7 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
}) => {
const i18n = useOptionalI18n();
const t = i18n?.t ?? defaultTranslate;
const clearSqlLogs = useStore(state => state.clearSqlLogs);
const globalHiddenColumns = useGlobalHiddenColumns();
const [draggingResultKey, setDraggingResultKey] = useState<string | null>(null);
const [detachDragPreview, setDetachDragPreview] = useState<DetachDragPreviewState | null>(null);
@@ -696,11 +698,24 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
const tabsHideButton = (
<Tooltip title={hideTooltipTitle}>
<Button aria-label={t('query_editor.results_panel.aria.hide')} className="query-result-panel-hide query-result-panel-hide-compact" type="text" size="small" icon={<EyeInvisibleOutlined />} onClick={onHide} />
<Button aria-label={t('query_editor.results_panel.aria.hide')} className="query-result-panel-hide query-result-panel-tab-action" type="text" size="small" icon={<EyeInvisibleOutlined />} onClick={onHide} />
</Tooltip>
);
const tabsClearButton = (
<Tooltip title={t('log_panel.action.clear')}>
<Button aria-label={t('log_panel.action.clear')} className="query-result-panel-clear query-result-panel-tab-action" type="text" size="small" icon={<ClearOutlined />} onClick={clearSqlLogs} />
</Tooltip>
);
const isSqlLogActive = resolvedActiveResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY;
const tabsExtraContent = !activeResultUsesDataGrid
? { right: <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>{tabsHideButton}</div> }
? {
right: (
<div className="query-result-panel-tab-actions">
{isSqlLogActive && tabsClearButton}
{tabsHideButton}
</div>
),
}
: undefined;
return (
@@ -736,7 +751,8 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
.query-result-panel-header { flex: 0 0 auto; min-height: 38px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 12px; border-bottom: 1px solid rgba(0, 0, 0, 0.06); background: rgba(255, 255, 255, 0.9); }
.query-result-panel-header-title { font-size: 13px; font-weight: 600; color: #666; }
.query-result-panel-hide { display: inline-flex; align-items: center; gap: 4px; }
.query-result-panel-hide-compact { min-width: 28px; padding: 0 6px; justify-content: center; }
.query-result-panel-tab-actions { display: inline-flex; flex-direction: row; align-items: center; gap: 4px; }
.query-result-tabs .ant-tabs-extra-content .query-result-panel-tab-action { width: 28px; min-width: 28px; height: 28px !important; min-height: 28px !important; padding: 0 !important; display: inline-flex; align-items: center; justify-content: center; }
`}</style>
<div data-gonavi-close-shortcut-scope="result" className={isV2Ui ? 'gn-v2-query-results' : undefined} style={{ position: 'relative', flex: 1, minHeight: 0, overflow: 'hidden', padding: 0, display: 'flex', flexDirection: 'column' }}>
{tabItems.length > 0 ? (

View File

@@ -32,6 +32,38 @@ describe('QueryEditorToolbar layout', () => {
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-action-pair {');
});
it('shares the active theme surface across the SQL toolbar and Monaco editor', () => {
const css = readV2ThemeCss();
const defaultMonacoCss = css.slice(
css.indexOf('body[data-ui-version="v2"]:not([data-custom-theme]) {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-editor {'),
);
const editorCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-editor {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-editor-pane {'),
);
const toolbarCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar-main {'),
);
const toolbarSelectCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selector {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selection-item,'),
);
const monacoStageCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-monaco-stage {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-monaco-stage:has('),
);
expect(defaultMonacoCss).toContain('--gn-monaco-bg: var(--gn-bg-panel-2);');
expect(editorCss).toContain('--gn-query-workbench-bg: var(--gn-bg-panel-2);');
expect(toolbarCss).toContain('background: var(--gn-query-workbench-bg) !important;');
expect(toolbarSelectCss).toContain('background: var(--gn-query-workbench-bg) !important;');
expect(monacoStageCss).toContain(
'background: var(--gn-monaco-bg, var(--gn-query-workbench-bg));',
);
});
it('keeps run and stop buttons separated in the v2 toolbar action group', () => {
const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');
const css = readV2ThemeCss();

View File

@@ -1,5 +1,11 @@
/* ─── V2 Query editor ─ */
/* Base themes follow the sidebar surface; custom themes keep an explicit Monaco override. */
body[data-ui-version="v2"]:not([data-custom-theme]) {
--gn-monaco-bg: var(--gn-bg-panel-2);
}
body[data-ui-version="v2"] .gn-v2-query-editor {
--gn-query-workbench-bg: var(--gn-bg-panel-2);
background: var(--gn-bg-app);
}
@@ -95,7 +101,7 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
background: var(--gn-bg-panel) !important;
background: var(--gn-query-workbench-bg) !important;
border-bottom: 0.5px solid var(--gn-br-1) !important;
}
@@ -198,6 +204,7 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-select-icon {
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selector {
height: 32px !important;
padding: 0 10px !important;
background: var(--gn-query-workbench-bg) !important;
border-radius: 9px !important;
}
@@ -356,8 +363,7 @@ body[data-ui-version="v2"] .gn-v2-query-monaco-stage {
overflow: hidden;
border-top: 0.5px solid var(--gn-br-1);
border-bottom: 0.5px solid var(--gn-br-1);
/* Same token as Monaco surface */
background: var(--gn-monaco-bg, var(--gn-bg-panel));
background: var(--gn-monaco-bg, var(--gn-query-workbench-bg));
}
body[data-ui-version="v2"] .gn-v2-query-monaco-stage:has(.monaco-editor .find-widget.visible:not(.hiddenEditor)) {
@@ -367,7 +373,7 @@ body[data-ui-version="v2"] .gn-v2-query-monaco-stage:has(.monaco-editor .find-wi
body[data-ui-version="v2"] .gn-v2-query-monaco-shell {
min-height: 0;
overflow: visible;
background: var(--gn-monaco-bg, var(--gn-bg-panel));
background: var(--gn-monaco-bg, var(--gn-query-workbench-bg));
}
.gn-query-execution-statusbar {
@@ -423,24 +429,25 @@ body[data-ui-version="v2"] .gn-v2-query-results {
}
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav {
box-sizing: border-box;
background: var(--gn-bg-panel-2);
padding-left: 8px;
min-height: 38px;
padding: 0 8px 0 0;
min-height: 46px;
}
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-extra-content {
display: inline-flex;
align-items: center;
padding-right: 8px;
padding-right: 0;
}
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab {
width: auto !important;
min-width: 0 !important;
max-width: 148px !important;
height: 30px !important;
min-height: 30px;
margin: 4px 6px 4px 0 !important;
height: 46px !important;
min-height: 46px;
margin: 0 !important;
padding: 0 9px !important;
border: 0.5px solid var(--gn-br-1) !important;
border-radius: 8px !important;
@@ -451,6 +458,11 @@ body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-n
font-size: 14px;
}
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-wrap,
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-list {
min-height: 46px;
}
body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-list {
align-items: center;
width: auto;
@@ -503,6 +515,10 @@ body[data-ui-version="v2"] .gn-v2-query-result-panel-header {
border-bottom-color: var(--gn-br-1);
}
body[data-ui-version="v2"] .log-panel-embedded .log-panel-table .ant-table {
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
body[data-ui-version="v2"] .gn-v2-query-result-panel-header .query-result-panel-header-title {
color: var(--gn-fg-3);
}

View File

@@ -51,11 +51,18 @@ describe('custom theme CSS validation', () => {
);
});
it('injects --gn-monaco-bg for hand-written themes that only set panel', () => {
it('injects --gn-monaco-bg for hand-written themes that only set theme surfaces', () => {
const css = 'body[data-custom-theme] {\n --gn-bg-panel: #f7faf8;\n --gn-bg-panel-2: #eef3f0;\n}';
const next = ensureCustomThemeMonacoSurfaceVars(css);
expect(next).toContain('--gn-monaco-bg: var(--gn-bg-panel-2)');
expect(next).toContain('--gn-bg-panel: #f7faf8');
});
it('keeps legacy hand-written themes aligned when they only define panel', () => {
const css = 'body[data-custom-theme] {\n --gn-bg-panel: #f7faf8;\n}';
const next = ensureCustomThemeMonacoSurfaceVars(css);
expect(next).toContain('--gn-monaco-bg: var(--gn-bg-panel)');
expect(next).toContain('--gn-bg-panel: #f7faf8');
});
it('does not override an explicit --gn-monaco-bg in custom theme CSS', () => {

View File

@@ -220,19 +220,20 @@ export const createCustomThemeId = (): string => {
/**
* Ensure Monaco surface token exists for hand-written custom themes.
* Presets already define it; user CSS that only sets --gn-bg-panel still needs this.
* Presets already define it; hand-written CSS still needs the shared theme surface default.
* If the theme already declares --gn-monaco-bg, leave it alone (user override wins).
*/
export const ensureCustomThemeMonacoSurfaceVars = (css: string): string => {
const source = String(css || '');
if (/--gn-monaco-bg\s*:/.test(source)) return source;
const surfaceVar = /--gn-bg-panel-2\s*:/.test(source) ? '--gn-bg-panel-2' : '--gn-bg-panel';
const trimmed = source.trimEnd();
const appendix = [
'',
'/* GoNavi: Monaco surface follows panel unless the theme overrides --gn-monaco-bg */',
`/* GoNavi: Monaco surface follows ${surfaceVar} unless the theme overrides --gn-monaco-bg */`,
'body[data-custom-theme],',
'body[data-custom-theme][data-ui-version="v2"] {',
' --gn-monaco-bg: var(--gn-bg-panel);',
` --gn-monaco-bg: var(${surfaceVar});`,
'}',
'',
].join('\n');
@@ -396,7 +397,7 @@ body[data-custom-theme][data-ui-version="v2"] {
--gn-bg-chrome: #171a23;
--gn-bg-panel: #1d202b;
--gn-bg-panel-2: #232733;
--gn-monaco-bg: var(--gn-bg-panel);
--gn-monaco-bg: var(--gn-bg-panel-2);
--gn-bg-hover: rgba(255, 255, 255, 0.06);
--gn-bg-active: rgba(255, 255, 255, 0.10);
--gn-bg-selected: rgba(139, 92, 246, 0.18);

View File

@@ -48,11 +48,15 @@ describe('built-in custom theme presets', () => {
expect(sanitizeCustomThemeDefinition(preset)).toEqual(expect.objectContaining({ id: preset.id }));
expect(preset.css).toContain('--gn-ant-primary:');
expect(preset.css).toContain('--gn-ant-on-primary:');
expect(preset.css).toContain('--gn-monaco-bg: var(--gn-bg-panel-2);');
expect(preset.css).toContain('--gn-settings-card-bg:');
expect(preset.css).toContain('--gn-explain-critical:');
expect(preset.css).toContain('--gn-status-connected:');
expect(preset.css).toContain('.gn-v2-tab-label-part-host');
expect(preset.css).toContain('.gn-v2-tab-label-part-database');
expect(preset.css).toContain(
'background-color: var(--gn-monaco-bg, var(--gn-bg-panel-2)) !important;',
);
}
expect(BUILTIN_CUSTOM_THEME_PRESETS.filter((preset) => preset.baseMode === 'dark')).toHaveLength(4);
expect(BUILTIN_CUSTOM_THEME_PRESETS.filter((preset) => preset.baseMode === 'light')).toHaveLength(2);
@@ -70,6 +74,7 @@ describe('built-in custom theme presets', () => {
expect(comfortDark.css).toContain('.gn-v2-query-toolbar-save-action');
expect(comfortDark.css).toContain('.gn-v2-ai-panel .ai-logo');
expect(comfortDark.css).toContain('.monaco-editor-background');
expect(comfortDark.css).not.toContain('background-color: var(--gn-bg-input) !important;');
});
it('keeps preset text and solid-button colors at WCAG AA contrast', () => {

View File

@@ -254,7 +254,7 @@ body[data-custom-theme][data-ui-version="v2"] {
--gn-bg-chrome: ${palette.chrome};
--gn-bg-panel: ${palette.panel};
--gn-bg-panel-2: ${palette.panel2};
--gn-monaco-bg: var(--gn-bg-panel);
--gn-monaco-bg: var(--gn-bg-panel-2);
--gn-bg-input: ${palette.input};
--gn-bg-subtle: ${palette.panel2};
--gn-bg-hover: ${palette.hover};
@@ -455,7 +455,7 @@ body[data-custom-theme][data-ui-version="v2"] .monaco-editor,
body[data-custom-theme][data-ui-version="v2"] .monaco-editor-background,
body[data-custom-theme][data-ui-version="v2"] .monaco-editor .margin,
body[data-custom-theme][data-ui-version="v2"] .monaco-editor .sticky-widget {
background-color: var(--gn-bg-input) !important;
background-color: var(--gn-monaco-bg, var(--gn-bg-panel-2)) !important;
}`;
const createPreset = (