feat(grid): configure cell detail button visibility

Closes #5593
This commit is contained in:
t8y2
2026-08-24 19:54:35 +00:00
parent 3232195e1f
commit 68b0ab0476
17 changed files with 146 additions and 9 deletions
@@ -369,6 +369,7 @@ const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEnt
const editDataGridFilterEditorView = ref<DataGridFilterEditorView>(settingsStore.editorSettings.dataGridFilterEditorView);
const editDataGridTextFilterPanelHeight = ref(settingsStore.editorSettings.dataGridTextFilterPanelHeight);
const editDataGridAutoTransposeSingleRow = ref(settingsStore.editorSettings.dataGridAutoTransposeSingleRow);
const editDataGridCellDetailButtonVisible = ref(settingsStore.editorSettings.dataGridCellDetailButtonVisible);
const editPageSize = ref(settingsStore.editorSettings.pageSize);
const editTableOpenPageSize = ref(settingsStore.editorSettings.tableOpenPageSize);
const editQueryResultMaxRowsEnabled = ref(settingsStore.editorSettings.queryResultMaxRowsEnabled);
@@ -557,6 +558,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
dataGridFilterEditorView: editDataGridFilterEditorView.value,
dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight.value,
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow.value,
dataGridCellDetailButtonVisible: editDataGridCellDetailButtonVisible.value,
flatteningMultiLineText: editFlatteningMultiLineText.value,
pageSize: editPageSize.value,
tableOpenPageSize: editTableOpenPageSize.value,
@@ -845,6 +847,7 @@ function syncEditorSettingsDraftFromStore() {
editDataGridFilterEditorView.value = settingsStore.editorSettings.dataGridFilterEditorView;
editDataGridTextFilterPanelHeight.value = settingsStore.editorSettings.dataGridTextFilterPanelHeight;
editDataGridAutoTransposeSingleRow.value = settingsStore.editorSettings.dataGridAutoTransposeSingleRow;
editDataGridCellDetailButtonVisible.value = settingsStore.editorSettings.dataGridCellDetailButtonVisible;
editFlatteningMultiLineText.value = settingsStore.editorSettings.flatteningMultiLineText;
editPageSize.value = settingsStore.editorSettings.pageSize;
editTableOpenPageSize.value = settingsStore.editorSettings.tableOpenPageSize;
@@ -1139,6 +1142,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
editDataGridCellDetailButtonVisible.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
@@ -1218,6 +1222,7 @@ function resetAllDefaults() {
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
editDataGridCellDetailButtonVisible.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
@@ -5305,6 +5310,17 @@ onUnmounted(() => {
</div>
<Switch id="data-grid-auto-transpose-single-row" v-model="editDataGridAutoTransposeSingleRow" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="data-grid-cell-detail-button-visible">
{{ t("settings.dataGridCellDetailButtonVisible") }}
</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.dataGridCellDetailButtonVisibleDescription") }}
</p>
</div>
<Switch id="data-grid-cell-detail-button-visible" v-model="editDataGridCellDetailButtonVisible" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="flattening-multi-line">
@@ -362,6 +362,7 @@ const slots = useSlots();
const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
const settingsStore = useSettingsStore();
const cellDetailButtonEnabled = computed(() => settingsStore.editorSettings.dataGridCellDetailButtonVisible);
const tableFontSize = computed(() => settingsStore.editorSettings.tableFontSize);
const rowNumberWidth = ref(DATA_GRID_ROW_NUM_WIDTH);
const largeValueResolutionVersion = ref(0);
@@ -7296,7 +7297,8 @@ const canvasDetailButtonCell = computed(() => {
const visibleRight = viewportWidth > 0 ? Math.min(viewportWidth, rect.left + rect.width) : rect.left + rect.width;
const canQuickDownload = canQuickDownloadCellValue(target.rowIndex, target.col);
const foreignKey = canvasCellForeignKey(target.rowIndex, target.col);
const minWidth = canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey) + 2;
if (!cellDetailButtonEnabled.value && !canQuickDownload && !foreignKey) return null;
const minWidth = canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey, cellDetailButtonEnabled.value) + 2;
if (rect.top < 0 || rect.top > viewportHeight - 1 || visibleRight - visibleLeft < minWidth) return null;
return {
rowIndex: target.rowIndex,
@@ -7311,7 +7313,7 @@ const canvasDetailButtonCell = computed(() => {
const canvasDetailButtonStyle = computed(() => {
const cell = canvasDetailButtonCell.value;
if (!cell) return {};
const actionWidth = canvasDataGridActionOverlayWidth(cell.canQuickDownload, !!cell.foreignKey);
const actionWidth = canvasDataGridActionOverlayWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value);
const edgeGap = 6;
return {
left: `${Math.max(rowNumberWidth.value, cell.rect.left + cell.rect.width - actionWidth - edgeGap)}px`,
@@ -7325,7 +7327,7 @@ const canvasRightAlignedActionCell = computed(() => {
return {
rowIndex: cell.rowIndex,
visibleColIdx: cell.visibleColIdx,
reservedWidth: canvasDataGridActionReservedWidth(cell.canQuickDownload, !!cell.foreignKey),
reservedWidth: canvasDataGridActionReservedWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value),
};
});
@@ -7412,6 +7414,7 @@ watch(
hoveredDetailCell,
detailCell,
showCellDetail,
cellDetailButtonEnabled,
editingCell,
frozenColumnCount,
saveError,
@@ -12083,6 +12086,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] {
</template>
</LightDropdownMenu>
<button
v-if="cellDetailButtonEnabled"
class="flex h-5 w-5 items-center justify-center rounded bg-background/90 text-muted-foreground shadow-sm ring-1 ring-border hover:text-foreground"
:title="t('grid.cellDetails')"
@mousedown.stop
@@ -12816,6 +12820,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] {
<ArrowUpRight class="h-3 w-3" />
</button>
<button
v-if="cellDetailButtonEnabled"
class="flex h-5 w-5 items-center justify-center rounded bg-background/90 text-muted-foreground shadow-sm ring-1 ring-border hover:text-foreground"
:title="t('grid.cellDetails')"
@mousedown.stop
@@ -13031,6 +13036,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] {
</template>
</LightDropdownMenu>
<button
v-if="cellDetailButtonEnabled"
class="flex h-5 w-5 items-center justify-center rounded bg-background/90 text-muted-foreground shadow-sm ring-1 ring-border hover:text-foreground"
:title="t('grid.cellDetails')"
@mousedown.stop
@@ -4,6 +4,29 @@ import { describe, expect, it } from "vitest";
const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), "utf8");
describe("DataGrid cell detail selection", () => {
it("gates only the three hover Info buttons with the visibility setting", () => {
expect(dataGridSource).toContain("const cellDetailButtonEnabled = computed(() => settingsStore.editorSettings.dataGridCellDetailButtonVisible);");
expect(dataGridSource.match(/v-if="cellDetailButtonEnabled"/g)).toHaveLength(3);
expect(dataGridSource.match(/v-if="cellDetailButtonVisible\(/g)).toHaveLength(2);
const hoverVisibility = dataGridSource.match(/function cellDetailButtonVisible[\s\S]*?\n\}/)?.[0];
expect(hoverVisibility).toBeDefined();
expect(hoverVisibility).not.toContain("dataGridCellDetailButtonVisible");
});
it("removes the Canvas action overlay and reservation only when no action remains", () => {
expect(dataGridSource).toContain("if (!cellDetailButtonEnabled.value && !canQuickDownload && !foreignKey) return null;");
expect(dataGridSource).toContain("canvasDataGridActionOverlayWidth(canQuickDownload, !!foreignKey, cellDetailButtonEnabled.value)");
expect(dataGridSource).toContain("canvasDataGridActionOverlayWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value)");
expect(dataGridSource).toContain("canvasDataGridActionReservedWidth(cell.canQuickDownload, !!cell.foreignKey, cellDetailButtonEnabled.value)");
expect(dataGridSource).toMatch(/watch\([\s\S]*?cellDetailButtonEnabled,[\s\S]*?scheduleCanvasDraw/);
});
it("keeps context-menu details and refresh restoration independent of the hover setting", () => {
expect(dataGridSource).toContain("cellDetails: openContextCellDetailDialog");
expect(dataGridSource).toContain("if (cellDialog) openCellDetailDialog(cellDialog.rowIndex, cellDialog.col);");
});
it("resynchronizes the open detail after a mouse selection gesture finishes", () => {
expect(dataGridSource).toContain("watch([selectedRange, showCellDetail, isEditingDetail, isSelectingCells]");
expect(dataGridSource).toContain("if (isSelectingCells.value) return;");
+2
View File
@@ -6227,6 +6227,8 @@ export default {
dataGridQuickEntryDescription: "When enabled, editing a cell or filling the bottom blank row saves to the database as soon as focus leaves. Useful for frequent entry, but it can cause accidental writes.",
dataGridAutoTransposeSingleRow: "Auto-transpose single-row query results",
dataGridAutoTransposeSingleRowDescription: "When enabled, SQL query results with exactly one row and multiple columns automatically switch to transpose view.",
dataGridCellDetailButtonVisible: "Show cell detail button",
dataGridCellDetailButtonVisibleDescription: "Show the cell detail button when hovering over a cell.",
infiniteScroll: "Infinite scroll loading",
autoCalculateTotalRows: "Auto-calculate total row count",
autoCalculateTotalRowsDescription: "Run COUNT(*) automatically after each query to show the total matching rows. Off by default to keep large queries fast — calculate it on demand from the result footer.",
+2
View File
@@ -5947,6 +5947,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "Al activarlo, editar una celda o llenar la fila vacía inferior se guarda en la base de datos al perder el foco. Agiliza la captura frecuente, pero puede causar escrituras accidentales.",
dataGridAutoTransposeSingleRow: "Transponer automáticamente los resultados de consulta de una sola fila",
dataGridAutoTransposeSingleRowDescription: "Al activarlo, los resultados de consultas SQL con exactamente una fila y varias columnas cambian automáticamente a la vista transpuesta.",
dataGridCellDetailButtonVisible: "Mostrar botón de detalles de celda",
dataGridCellDetailButtonVisibleDescription: "Mostrar el botón de detalles de celda al pasar el cursor sobre una celda.",
infiniteScroll: "Carga de desplazamiento infinito",
autoCalculateTotalRows: "Calcular automáticamente el total de filas",
autoCalculateTotalRowsDescription: "Ejecuta COUNT(*) automáticamente tras cada consulta para mostrar el total de filas coincidentes. Desactivado por defecto para mantener rápidas las consultas grandes; puedes calcularlo cuando quieras desde el pie de resultados.",
+2
View File
@@ -5947,6 +5947,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "Se attivo, la modifica di una cella o la compilazione della riga vuota in fondo viene salvata nel database appena il focus esce. Utile per inserimenti frequenti, ma può causare scritture accidentali.",
dataGridAutoTransposeSingleRow: "Trasponi automaticamente i risultati delle query a riga singola",
dataGridAutoTransposeSingleRowDescription: "Quando è attivata, i risultati delle query SQL con esattamente una riga e più colonne passano automaticamente alla vista trasposta.",
dataGridCellDetailButtonVisible: "Mostra il pulsante dei dettagli della cella",
dataGridCellDetailButtonVisibleDescription: "Mostra il pulsante dei dettagli della cella al passaggio del mouse su una cella.",
infiniteScroll: "Caricamento a scorrimento infinito",
autoCalculateTotalRows: "Calcola automaticamente il totale delle righe",
autoCalculateTotalRowsDescription: "Esegue COUNT(*) automaticamente dopo ogni query per mostrare il totale delle righe corrispondenti. Disattivato per impostazione predefinita per mantenere veloci le query grandi; puoi calcolarlo all'occorrenza dal piè di pagina dei risultati.",
+2
View File
@@ -5981,6 +5981,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "有効にすると、セル編集や下部の空行入力はフォーカスが外れた時点でデータベースに保存されます。頻繁な入力に便利ですが、誤操作による書き込みのリスクがあります。",
dataGridAutoTransposeSingleRow: "1 行のクエリ結果を自動で転置表示",
dataGridAutoTransposeSingleRowDescription: "有効にすると、1 行かつ複数列の SQL クエリ結果が自動的に転置表示に切り替わります。",
dataGridCellDetailButtonVisible: "セル詳細ボタンを表示",
dataGridCellDetailButtonVisibleDescription: "セルにカーソルを合わせたときにセル詳細ボタンを表示します。",
tableColumnTemplateFields: "新規テーブルのプリセット列",
tableColumnTemplateFieldsDescription: "データベース種別を選択し、新規テーブル作成時に使うプリセット列の型を設定します。",
tableColumnTemplateAdd: "列を追加",
+2
View File
@@ -5630,6 +5630,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "활성화하면 셀을 편집하거나 아래 빈 행을 채운 후 포커스가 벗어나는 즉시 데이터베이스에 저장합니다. 빈번한 입력에 유용하지만 실수로 쓰기를 유발할 수 있습니다.",
dataGridAutoTransposeSingleRow: "단일 행 쿼리 결과 자동 전치",
dataGridAutoTransposeSingleRowDescription: "활성화하면 정확히 한 행과 여러 열이 있는 SQL 쿼리 결과가 자동으로 전치 보기로 전환됩니다.",
dataGridCellDetailButtonVisible: "셀 세부 정보 버튼 표시",
dataGridCellDetailButtonVisibleDescription: "셀 위에 마우스를 올리면 셀 세부 정보 버튼을 표시합니다.",
infiniteScroll: "무한 스크롤 로딩",
autoCalculateTotalRows: "전체 행 수 자동 집계",
autoCalculateTotalRowsDescription: "각 쿼리 후 자동으로 COUNT(*)를 실행하여 일치하는 전체 행 수를 표시합니다. 대규모 쿼리를 빠르게 유지하기 위해 기본적으로 꺼져 있으며, 결과 푸터에서 요청 시 집계할 수 있습니다.",
+2
View File
@@ -5949,6 +5949,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "Quando ativado, editar uma célula ou preencher a linha vazia inferior salva no banco de dados assim que o foco sai. Ajuda na entrada frequente, mas pode causar gravações acidentais.",
dataGridAutoTransposeSingleRow: "Transpor automaticamente resultados de consulta com uma única linha",
dataGridAutoTransposeSingleRowDescription: "Quando ativado, os resultados de consultas SQL com exatamente uma linha e várias colunas alternam automaticamente para a visualização transposta.",
dataGridCellDetailButtonVisible: "Mostrar botão de detalhes da célula",
dataGridCellDetailButtonVisibleDescription: "Mostrar o botão de detalhes da célula ao passar o mouse sobre uma célula.",
infiniteScroll: "Carregamento por rolagem infinita",
autoCalculateTotalRows: "Calcular automaticamente o total de linhas",
autoCalculateTotalRowsDescription: "Executa COUNT(*) automaticamente após cada consulta para mostrar o total de linhas correspondentes. Desativado por padrão para manter consultas grandes rápidas; você pode calculá-lo quando quiser no rodapé dos resultados.",
+2
View File
@@ -6211,6 +6211,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "开启后,编辑单元格或填写底部空行并离焦会立即保存到数据库。便于高频录入,但存在误操作风险。",
dataGridAutoTransposeSingleRow: "自动转置单行查询结果",
dataGridAutoTransposeSingleRowDescription: "开启后,SQL 查询结果只有一行且包含多列时,自动切换为转置视图。",
dataGridCellDetailButtonVisible: "显示单元格详情按钮",
dataGridCellDetailButtonVisibleDescription: "鼠标悬停在单元格上时显示单元格详情按钮。",
infiniteScroll: "无限滚动加载",
autoCalculateTotalRows: "自动统计总行数",
autoCalculateTotalRowsDescription: "每次查询后自动执行 COUNT(*) 显示匹配的总行数。默认关闭以保证大查询速度 —— 可在结果栏按需手动统计。",
+2
View File
@@ -5266,6 +5266,8 @@ export default withEnglishFallback({
dataGridQuickEntryDescription: "開啟後,編輯儲存格或填寫底部空白列並離焦會立即儲存到資料庫。適合高頻輸入,但有誤操作風險。",
dataGridAutoTransposeSingleRow: "自動轉置單行查詢結果",
dataGridAutoTransposeSingleRowDescription: "開啟後,SQL 查詢結果只有一行且包含多個欄位時,會自動切換為轉置檢視。",
dataGridCellDetailButtonVisible: "顯示儲存格詳細資料按鈕",
dataGridCellDetailButtonVisibleDescription: "游標懸停在儲存格上時顯示儲存格詳細資料按鈕。",
infiniteScroll: "無限滾動載入",
autoCalculateTotalRows: "自動統計總筆數",
autoCalculateTotalRowsDescription: "每次查詢後自動執行 COUNT(*) 顯示符合的總筆數。預設關閉以確保大型查詢速度 —— 可在結果列按需手動統計。",
@@ -1,7 +1,7 @@
// @vitest-environment happy-dom
import { beforeEach, describe, expect, it, vi } from "vitest";
import { drawCanvasDataGrid, resolveCanvasBackingStoreMetrics, type DrawCanvasDataGridOptions } from "@/lib/dataGrid/canvasDataGridRenderer";
import { canvasDataGridActionOverlayWidth, canvasDataGridActionReservedWidth, drawCanvasDataGrid, resolveCanvasBackingStoreMetrics, type DrawCanvasDataGridOptions } from "@/lib/dataGrid/canvasDataGridRenderer";
function createMockCanvas(width = 800, height = 400) {
const canvas = document.createElement("canvas");
@@ -80,6 +80,18 @@ describe("drawCanvasDataGrid with frozen columns", () => {
} as CSSStyleDeclaration);
});
it.each([
[false, false, true, 22, 28],
[false, false, false, 0, 0],
[true, false, false, 22, 28],
[false, true, false, 22, 28],
[true, true, false, 44, 50],
[true, true, true, 66, 72],
])("reserves only enabled Canvas actions (download=%s, foreign key=%s, detail=%s)", (canQuickDownload, canNavigateForeignKey, showCellDetail, overlayWidth, reservedWidth) => {
expect(canvasDataGridActionOverlayWidth(canQuickDownload, canNavigateForeignKey, showCellDetail)).toBe(overlayWidth);
expect(canvasDataGridActionReservedWidth(canQuickDownload, canNavigateForeignKey, showCellDetail)).toBe(reservedWidth);
});
it("uses the exact observed device-pixel size instead of a nominal DPR", () => {
expect(
resolveCanvasBackingStoreMetrics({
@@ -195,13 +195,14 @@ export function fitCanvasText(ctx: CanvasRenderingContext2D, text: string, maxWi
return result;
}
export function canvasDataGridActionReservedWidth(canQuickDownload: boolean, canNavigateForeignKey = false): number {
return canvasDataGridActionOverlayWidth(canQuickDownload, canNavigateForeignKey) + 6;
export function canvasDataGridActionReservedWidth(canQuickDownload: boolean, canNavigateForeignKey = false, showCellDetail = true): number {
const overlayWidth = canvasDataGridActionOverlayWidth(canQuickDownload, canNavigateForeignKey, showCellDetail);
return overlayWidth > 0 ? overlayWidth + 6 : 0;
}
/** 悬浮按钮组宽度:每个按钮 20px + 2px 间距(detail 按钮始终存在) */
export function canvasDataGridActionOverlayWidth(canQuickDownload: boolean, canNavigateForeignKey = false): number {
return 22 + (canQuickDownload ? 22 : 0) + (canNavigateForeignKey ? 22 : 0);
/** 悬浮按钮组宽度:每个已启用按钮 20px + 2px 间距。 */
export function canvasDataGridActionOverlayWidth(canQuickDownload: boolean, canNavigateForeignKey = false, showCellDetail = true): number {
return (showCellDetail ? 22 : 0) + (canQuickDownload ? 22 : 0) + (canNavigateForeignKey ? 22 : 0);
}
export function resolveCanvasCellTextLayout(options: { drawX: number; colWidth: number; dpr: number; isRightAlign: boolean; reservedWidth?: number }): { textAnchorX: number; maxWidth: number } {
@@ -1,7 +1,10 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { EDITOR_SETTINGS_DRAFT_KEYS, editorSettingsDraftFromSettings, editorSettingsDraftChanged, editorSettingsPatchFromDraft, normalizeQueryResultMaxRowsDraft, normalizeTableOpenPageSizeDraft, shouldConfirmEditorSettingsDialogClose } from "../editorSettingsDraft";
import type { EditorSettings } from "@/stores/settingsStore";
const settingsDialogSource = readFileSync(new URL("../../../components/editor/EditorSettingsDialog.vue", import.meta.url), "utf8");
function makeSettings(overrides: Partial<EditorSettings> = {}): EditorSettings {
return {
autoCalculateTotalRows: false,
@@ -97,6 +100,10 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridTextFilterPanelHeight");
});
it("includes the cell detail button visibility", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridCellDetailButtonVisible");
});
it("includes completionTriggerMode", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("completionTriggerMode");
});
@@ -106,6 +113,16 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
});
});
describe("cell detail button settings control", () => {
it("binds the switch through apply and both reset paths", () => {
expect(settingsDialogSource).toContain("const editDataGridCellDetailButtonVisible = ref(settingsStore.editorSettings.dataGridCellDetailButtonVisible)");
expect(settingsDialogSource).toContain("dataGridCellDetailButtonVisible: editDataGridCellDetailButtonVisible.value");
expect(settingsDialogSource).toContain("editDataGridCellDetailButtonVisible.value = settingsStore.editorSettings.dataGridCellDetailButtonVisible");
expect(settingsDialogSource.match(/editDataGridCellDetailButtonVisible\.value = DEFAULT_EDITOR_SETTINGS\.dataGridCellDetailButtonVisible/g)).toHaveLength(2);
expect(settingsDialogSource).toContain('id="data-grid-cell-detail-button-visible" v-model="editDataGridCellDetailButtonVisible"');
});
});
describe("editorSettingsDraftFromSettings", () => {
it("round-trips a hidden line-number preference through the draft patch", () => {
const settings = makeSettings({ showLineNumbers: true });
@@ -281,6 +298,15 @@ describe("editorSettingsDraftChanged", () => {
});
describe("editorSettingsPatchFromDraft", () => {
it("applies, cancels, and re-enables the cell detail button visibility", () => {
const visible = editorSettingsDraftFromSettings(makeSettings({ dataGridCellDetailButtonVisible: true }));
const hidden = editorSettingsDraftFromSettings(makeSettings({ dataGridCellDetailButtonVisible: false }));
expect(editorSettingsPatchFromDraft(hidden, visible)).toEqual({ dataGridCellDetailButtonVisible: false });
expect(editorSettingsPatchFromDraft(visible, visible)).toEqual({});
expect(editorSettingsPatchFromDraft(visible, hidden)).toEqual({ dataGridCellDetailButtonVisible: true });
});
it("includes continueOnErrorOnBatch in patch when changed", () => {
const settings = makeSettings({ continueOnErrorOnBatch: false });
const draft = editorSettingsDraftFromSettings(settings);
@@ -44,6 +44,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"dataGridFilterEditorView",
"dataGridTextFilterPanelHeight",
"dataGridAutoTransposeSingleRow",
"dataGridCellDetailButtonVisible",
"pageSize",
"tableOpenPageSize",
"queryResultMaxRowsEnabled",
+32
View File
@@ -279,6 +279,16 @@ describe("normalizeEditorSettings", () => {
expect(invalid.dataGridBooleanDisplayMode).toBe("dropdown");
});
it("defaults the cell detail hover button on and preserves only boolean values", () => {
expect(normalizeEditorSettings({}).dataGridCellDetailButtonVisible).toBe(true);
expect(normalizeEditorSettings({ dataGridCellDetailButtonVisible: true }).dataGridCellDetailButtonVisible).toBe(true);
expect(normalizeEditorSettings({ dataGridCellDetailButtonVisible: false }).dataGridCellDetailButtonVisible).toBe(false);
for (const invalidValue of [0, 1, "false", null]) {
expect(normalizeEditorSettings({ dataGridCellDetailButtonVisible: invalidValue as never }).dataGridCellDetailButtonVisible).toBe(true);
}
});
it("defaults the data grid font and preserves a custom font family", () => {
const defaultFontFamily = `"Geist Variable Tabular", "Geist Variable", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif`;
expect(normalizeEditorSettings({}).tableFontFamily).toBe(defaultFontFamily);
@@ -732,6 +742,28 @@ describe("settingsStore persisted settings initialization", () => {
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ openDataTabsNextToActive: false }));
});
it("loads, persists, and reloads the cell detail button visibility", async () => {
let persistedSettings: Record<string, unknown> = { dataGridCellDetailButtonVisible: false };
const loadEditorSettings = vi.fn(async () => JSON.parse(JSON.stringify(persistedSettings)));
const saveEditorSettings = vi.fn(async (settings: Record<string, unknown>) => {
persistedSettings = JSON.parse(JSON.stringify(settings));
});
vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings }));
const { useSettingsStore } = await import("@/stores/settingsStore");
const store = useSettingsStore();
await store.initEditorSettings();
expect(store.editorSettings.dataGridCellDetailButtonVisible).toBe(false);
await store.updateEditorSettingsAndPersist({ dataGridCellDetailButtonVisible: true });
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ dataGridCellDetailButtonVisible: true }));
setActivePinia(createPinia());
const restartedStore = useSettingsStore();
await restartedStore.initEditorSettings();
expect(restartedStore.editorSettings.dataGridCellDetailButtonVisible).toBe(true);
});
it("loads, persists, and reloads hidden query editor line numbers", async () => {
let persistedSettings: Record<string, unknown> = { showLineNumbers: true };
const loadEditorSettings = vi.fn(async () => JSON.parse(JSON.stringify(persistedSettings)));
+4
View File
@@ -573,6 +573,7 @@ export interface EditorSettings {
dataGridExtractorOptions: DataGridExtractorOptions;
resultRunDisplayMode: ResultRunDisplayMode;
dataGridAutoTransposeSingleRow: boolean;
dataGridCellDetailButtonVisible: boolean;
dataGridMultiRowTranspose: boolean;
dataGridHideNullColumns: boolean;
dataGridBooleanDisplayMode: "dropdown" | "checkbox";
@@ -781,6 +782,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
dataGridExtractorOptions: normalizeDataGridExtractorOptions(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS),
resultRunDisplayMode: "tabs",
dataGridAutoTransposeSingleRow: false,
dataGridCellDetailButtonVisible: true,
dataGridMultiRowTranspose: false,
dataGridHideNullColumns: false,
dataGridBooleanDisplayMode: "dropdown",
@@ -1154,6 +1156,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
dataGridExtractorOptions: normalizeDataGridExtractorOptions(settings.dataGridExtractorOptions),
resultRunDisplayMode: normalizeResultRunDisplayMode(settings.resultRunDisplayMode),
dataGridAutoTransposeSingleRow: settings.dataGridAutoTransposeSingleRow === true,
dataGridCellDetailButtonVisible: typeof settings.dataGridCellDetailButtonVisible === "boolean" ? settings.dataGridCellDetailButtonVisible : DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible,
dataGridMultiRowTranspose: settings.dataGridMultiRowTranspose === true,
dataGridHideNullColumns: settings.dataGridHideNullColumns === true,
dataGridBooleanDisplayMode: settings.dataGridBooleanDisplayMode === "checkbox" ? "checkbox" : "dropdown",
@@ -1766,6 +1769,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.dataGridExtractorOptions !== undefined) editorSettings.value.dataGridExtractorOptions = normalizeDataGridExtractorOptions(partial.dataGridExtractorOptions);
if (partial.resultRunDisplayMode !== undefined) editorSettings.value.resultRunDisplayMode = normalizeResultRunDisplayMode(partial.resultRunDisplayMode);
if (partial.dataGridAutoTransposeSingleRow !== undefined) editorSettings.value.dataGridAutoTransposeSingleRow = partial.dataGridAutoTransposeSingleRow === true;
if (partial.dataGridCellDetailButtonVisible !== undefined) editorSettings.value.dataGridCellDetailButtonVisible = typeof partial.dataGridCellDetailButtonVisible === "boolean" ? partial.dataGridCellDetailButtonVisible : DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
if (partial.dataGridMultiRowTranspose !== undefined) editorSettings.value.dataGridMultiRowTranspose = partial.dataGridMultiRowTranspose === true;
if (partial.dataGridHideNullColumns !== undefined) editorSettings.value.dataGridHideNullColumns = partial.dataGridHideNullColumns === true;
if (partial.dataGridBooleanDisplayMode !== undefined) editorSettings.value.dataGridBooleanDisplayMode = partial.dataGridBooleanDisplayMode === "dropdown" ? "dropdown" : "checkbox";