1 parent
2f464e0d33
commit
44dc2a88f8
14 files changed
+334
-2
No files matched your search
@@ -187,6 +187,7 @@ import {
|
|||||||
isCancelSearchShortcut,
|
isCancelSearchShortcut,
|
||||||
isCopyCurrentRowShortcut,
|
isCopyCurrentRowShortcut,
|
||||||
isDeleteCurrentRowShortcut,
|
isDeleteCurrentRowShortcut,
|
||||||
|
isEditTableStructureShortcut,
|
||||||
isFocusSearchShortcut,
|
isFocusSearchShortcut,
|
||||||
isGoToColumnShortcut,
|
isGoToColumnShortcut,
|
||||||
isGoToFirstPageShortcut,
|
isGoToFirstPageShortcut,
|
||||||
@@ -9040,6 +9041,12 @@ async function onGridKeydown(event: KeyboardEvent) {
|
|||||||
if (event.defaultPrevented) return;
|
if (event.defaultPrevented) return;
|
||||||
|
|
||||||
const targetAllowsNativeClipboard = eventTargetAllowsNativeClipboard(event);
|
const targetAllowsNativeClipboard = eventTargetAllowsNativeClipboard(event);
|
||||||
|
if (!targetAllowsNativeClipboard && props.context === "table-data" && canOpenTableStructureEditor.value && isEditTableStructureShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
openTableStructureEditor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!targetAllowsNativeClipboard && displayableColumnIndexes.value.length > 0 && isGoToColumnShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
if (!targetAllowsNativeClipboard && displayableColumnIndexes.value.length > 0 && isGoToColumnShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
+264
@@ -0,0 +1,264 @@
|
|||||||
|
// @vitest-environment happy-dom
|
||||||
|
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { createApp, defineComponent, h, markRaw, nextTick, type App, type PropType } from "vue";
|
||||||
|
import { createPinia, setActivePinia } from "pinia";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import i18n from "@/i18n";
|
||||||
|
import type { ColumnInfo, DatabaseType, QueryResult } from "@/types/database";
|
||||||
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
|
vi.mock("@/composables/useDataGridColumnResize", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("@/composables/useDataGridColumnResize")>();
|
||||||
|
const { ref } = await import("vue");
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useDataGridColumnResize: () => ({
|
||||||
|
initColumnWidths: vi.fn(),
|
||||||
|
onResizeStart: vi.fn(),
|
||||||
|
autoFitColumn: vi.fn(),
|
||||||
|
renderedColumnWidths: ref([120]),
|
||||||
|
totalWidth: ref(120),
|
||||||
|
columnVars: ref({ "--total-w": "120px" }),
|
||||||
|
getIsResizing: () => false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import DataGrid from "../DataGrid.vue";
|
||||||
|
import { useQueryStore } from "@/stores/queryStore";
|
||||||
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
|
|
||||||
|
const dataGridSource = readFileSync(resolve(import.meta.dirname, "../DataGrid.vue"), "utf8");
|
||||||
|
const mountedApps: Array<{ app: App; host: HTMLElement }> = [];
|
||||||
|
|
||||||
|
const RecycleScroller = defineComponent({
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: Array as PropType<unknown[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { attrs, slots }) {
|
||||||
|
return () =>
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
attrs,
|
||||||
|
props.items.map((item) => slots.default?.({ item })),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface TableMeta {
|
||||||
|
catalog?: string;
|
||||||
|
schema?: string;
|
||||||
|
tableName: string;
|
||||||
|
columns: ColumnInfo[];
|
||||||
|
primaryKeys: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountGrid(
|
||||||
|
options: {
|
||||||
|
context?: "results" | "table-data";
|
||||||
|
databaseType?: DatabaseType;
|
||||||
|
connectionId?: string;
|
||||||
|
database?: string;
|
||||||
|
tableMeta?: TableMeta | null;
|
||||||
|
shortcut?: string;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const pinia = createPinia();
|
||||||
|
setActivePinia(pinia);
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
settingsStore.updateEditorSettings({
|
||||||
|
dataGridRenderMode: "canvas",
|
||||||
|
shortcuts: {
|
||||||
|
...settingsStore.editorSettings.shortcuts,
|
||||||
|
editTableStructure: options.shortcut ?? "Mod+D",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const queryStore = useQueryStore();
|
||||||
|
const openTableStructure = vi.spyOn(queryStore, "openTableStructure").mockImplementation(() => "structure-tab");
|
||||||
|
const result = markRaw<QueryResult>({
|
||||||
|
columns: ["id"],
|
||||||
|
rows: [[1]],
|
||||||
|
affected_rows: 0,
|
||||||
|
execution_time_ms: 0,
|
||||||
|
});
|
||||||
|
const defaultTableMeta: TableMeta = {
|
||||||
|
catalog: "warehouse",
|
||||||
|
schema: "public",
|
||||||
|
tableName: "users",
|
||||||
|
columns: [],
|
||||||
|
primaryKeys: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const host = document.createElement("div");
|
||||||
|
document.body.append(host);
|
||||||
|
const Root = defineComponent({
|
||||||
|
setup() {
|
||||||
|
return () =>
|
||||||
|
h(
|
||||||
|
TooltipProvider,
|
||||||
|
{ delayDuration: 0 },
|
||||||
|
{
|
||||||
|
default: () =>
|
||||||
|
h(DataGrid, {
|
||||||
|
result,
|
||||||
|
databaseType: options.databaseType ?? "mysql",
|
||||||
|
context: options.context ?? "table-data",
|
||||||
|
connectionId: options.connectionId ?? "connection-1",
|
||||||
|
database: options.database ?? "app",
|
||||||
|
tableMeta: options.tableMeta === null ? undefined : (options.tableMeta ?? defaultTableMeta),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const app = createApp(Root);
|
||||||
|
app.use(pinia);
|
||||||
|
app.use(i18n);
|
||||||
|
app.component("RecycleScroller", RecycleScroller);
|
||||||
|
app.mount(host);
|
||||||
|
mountedApps.push({ app, host });
|
||||||
|
return { host, openTableStructure };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function settle() {
|
||||||
|
await nextTick();
|
||||||
|
await Promise.resolve();
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
function gridRoot(host: HTMLElement): HTMLElement {
|
||||||
|
const root = host.querySelector<HTMLElement>("[data-grid-root]");
|
||||||
|
if (!root) throw new Error("Data grid root not found");
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchModShortcut(target: HTMLElement, key: string): KeyboardEvent {
|
||||||
|
const event = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, ctrlKey: true, key });
|
||||||
|
target.dispatchEvent(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.getSelection()?.removeAllRanges();
|
||||||
|
for (const { app, host } of mountedApps.splice(0)) {
|
||||||
|
app.unmount();
|
||||||
|
host.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("DataGrid edit-table-structure shortcut", () => {
|
||||||
|
it("opens the existing structure editor route for an eligible table-data grid", async () => {
|
||||||
|
const { host, openTableStructure } = mountGrid();
|
||||||
|
await settle();
|
||||||
|
const root = gridRoot(host);
|
||||||
|
const bubbled = vi.fn();
|
||||||
|
host.addEventListener("keydown", bubbled);
|
||||||
|
root.focus();
|
||||||
|
|
||||||
|
const event = dispatchModShortcut(root, "d");
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(document.activeElement).toBe(root);
|
||||||
|
expect(event.defaultPrevented).toBe(true);
|
||||||
|
expect(bubbled).not.toHaveBeenCalled();
|
||||||
|
expect(openTableStructure).toHaveBeenCalledOnce();
|
||||||
|
expect(openTableStructure).toHaveBeenCalledWith("connection-1", "app", "public", "users", "ddl", undefined, "warehouse");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a configured shortcut and leaves the default key untouched", async () => {
|
||||||
|
const { host, openTableStructure } = mountGrid({ shortcut: "Mod+E" });
|
||||||
|
await settle();
|
||||||
|
const root = gridRoot(host);
|
||||||
|
const bubbled = vi.fn();
|
||||||
|
host.addEventListener("keydown", bubbled);
|
||||||
|
|
||||||
|
const defaultEvent = dispatchModShortcut(root, "d");
|
||||||
|
const customEvent = dispatchModShortcut(root, "e");
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(defaultEvent.defaultPrevented).toBe(false);
|
||||||
|
expect(customEvent.defaultPrevented).toBe(true);
|
||||||
|
expect(bubbled).toHaveBeenCalledOnce();
|
||||||
|
expect(openTableStructure).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["query result", { context: "results" as const }],
|
||||||
|
["unsupported database", { databaseType: "redis" as const }],
|
||||||
|
["missing connection", { connectionId: "" }],
|
||||||
|
["missing database", { database: "" }],
|
||||||
|
["missing table metadata", { tableMeta: null }],
|
||||||
|
])("does not consume the shortcut for %s", async (_label, options) => {
|
||||||
|
const { host, openTableStructure } = mountGrid(options);
|
||||||
|
await settle();
|
||||||
|
const root = gridRoot(host);
|
||||||
|
const bubbled = vi.fn();
|
||||||
|
host.addEventListener("keydown", bubbled);
|
||||||
|
|
||||||
|
const event = dispatchModShortcut(root, "d");
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(event.defaultPrevented).toBe(false);
|
||||||
|
expect(bubbled).toHaveBeenCalledOnce();
|
||||||
|
expect(openTableStructure).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves editable text targets untouched", async () => {
|
||||||
|
const { host, openTableStructure } = mountGrid();
|
||||||
|
await settle();
|
||||||
|
const root = gridRoot(host);
|
||||||
|
const bubbled = vi.fn();
|
||||||
|
host.addEventListener("keydown", bubbled);
|
||||||
|
const targets = [document.createElement("input"), document.createElement("textarea"), document.createElement("div"), document.createElement("div")];
|
||||||
|
targets[2]!.setAttribute("contenteditable", "true");
|
||||||
|
targets[3]!.setAttribute("role", "textbox");
|
||||||
|
|
||||||
|
for (const target of targets) {
|
||||||
|
root.append(target);
|
||||||
|
const event = dispatchModShortcut(target, "d");
|
||||||
|
expect(event.defaultPrevented).toBe(false);
|
||||||
|
}
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(bubbled).toHaveBeenCalledTimes(targets.length);
|
||||||
|
expect(openTableStructure).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves Ctrl+C for a native clipboard selection", async () => {
|
||||||
|
const { host, openTableStructure } = mountGrid();
|
||||||
|
await settle();
|
||||||
|
const root = gridRoot(host);
|
||||||
|
const region = document.createElement("div");
|
||||||
|
region.setAttribute("data-native-clipboard", "true");
|
||||||
|
region.textContent = "selected table information";
|
||||||
|
root.append(region);
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(region);
|
||||||
|
window.getSelection()?.addRange(range);
|
||||||
|
const bubbled = vi.fn();
|
||||||
|
host.addEventListener("keydown", bubbled);
|
||||||
|
|
||||||
|
const event = dispatchModShortcut(region, "c");
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(event.defaultPrevented).toBe(false);
|
||||||
|
expect(bubbled).toHaveBeenCalledOnce();
|
||||||
|
expect(openTableStructure).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("routes through the existing guarded action and keeps the button path", () => {
|
||||||
|
const start = dataGridSource.indexOf("async function onGridKeydown");
|
||||||
|
const end = dataGridSource.indexOf("function copyDetailValue", start);
|
||||||
|
const keydown = dataGridSource.slice(start, end);
|
||||||
|
|
||||||
|
expect(keydown).toMatch(
|
||||||
|
/if \(!targetAllowsNativeClipboard && props\.context === "table-data" && canOpenTableStructureEditor\.value && isEditTableStructureShortcut\(event, settingsStore\.editorSettings\.shortcuts\)\) \{[\s\S]*?event\.preventDefault\(\);[\s\S]*?event\.stopPropagation\(\);[\s\S]*?openTableStructureEditor\(\);[\s\S]*?return;/,
|
||||||
|
);
|
||||||
|
expect(dataGridSource).toMatch(/<Button v-if="canOpenTableStructureEditor"[^>]*@click="openTableStructureEditor">/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6530,6 +6530,7 @@ export default {
|
|||||||
shortcutRedo: "Redo",
|
shortcutRedo: "Redo",
|
||||||
shortcutSelectAll: "Select all",
|
shortcutSelectAll: "Select all",
|
||||||
shortcutExtendSelection: "Extend selection",
|
shortcutExtendSelection: "Extend selection",
|
||||||
|
shortcutEditTableStructure: "Edit table structure",
|
||||||
shortcutCopyCurrentRow: "Copy current data row",
|
shortcutCopyCurrentRow: "Copy current data row",
|
||||||
shortcutDeleteCurrentRow: "Delete current data row",
|
shortcutDeleteCurrentRow: "Delete current data row",
|
||||||
shortcutGoToColumn: "Go to column",
|
shortcutGoToColumn: "Go to column",
|
||||||
|
|||||||
@@ -6201,6 +6201,7 @@ export default withEnglishFallback({
|
|||||||
shortcutRedo: "Rehacer",
|
shortcutRedo: "Rehacer",
|
||||||
shortcutSelectAll: "Seleccionar todo",
|
shortcutSelectAll: "Seleccionar todo",
|
||||||
shortcutExtendSelection: "Ampliar selección",
|
shortcutExtendSelection: "Ampliar selección",
|
||||||
|
shortcutEditTableStructure: "Editar estructura de tabla",
|
||||||
shortcutCopyCurrentRow: "Copiar fila de datos actual",
|
shortcutCopyCurrentRow: "Copiar fila de datos actual",
|
||||||
shortcutDeleteCurrentRow: "Eliminar fila de datos actual",
|
shortcutDeleteCurrentRow: "Eliminar fila de datos actual",
|
||||||
shortcutGoToColumn: "Ir a la columna",
|
shortcutGoToColumn: "Ir a la columna",
|
||||||
|
|||||||
@@ -6201,6 +6201,7 @@ export default withEnglishFallback({
|
|||||||
shortcutRedo: "Ripeti",
|
shortcutRedo: "Ripeti",
|
||||||
shortcutSelectAll: "Seleziona tutto",
|
shortcutSelectAll: "Seleziona tutto",
|
||||||
shortcutExtendSelection: "Estendi selezione",
|
shortcutExtendSelection: "Estendi selezione",
|
||||||
|
shortcutEditTableStructure: "Modifica struttura tabella",
|
||||||
shortcutCopyCurrentRow: "Copia riga dati corrente",
|
shortcutCopyCurrentRow: "Copia riga dati corrente",
|
||||||
shortcutDeleteCurrentRow: "Elimina riga dati corrente",
|
shortcutDeleteCurrentRow: "Elimina riga dati corrente",
|
||||||
shortcutGoToColumn: "Vai alla colonna",
|
shortcutGoToColumn: "Vai alla colonna",
|
||||||
|
|||||||
@@ -6215,6 +6215,7 @@ export default withEnglishFallback({
|
|||||||
shortcutRedo: "やり直す",
|
shortcutRedo: "やり直す",
|
||||||
shortcutSelectAll: "すべて選択",
|
shortcutSelectAll: "すべて選択",
|
||||||
shortcutExtendSelection: "選択範囲を拡張",
|
shortcutExtendSelection: "選択範囲を拡張",
|
||||||
|
shortcutEditTableStructure: "テーブル構造を編集",
|
||||||
shortcutCopyCurrentRow: "現在のデータ行をコピー",
|
shortcutCopyCurrentRow: "現在のデータ行をコピー",
|
||||||
shortcutDeleteCurrentRow: "現在のデータ行を削除",
|
shortcutDeleteCurrentRow: "現在のデータ行を削除",
|
||||||
shortcutGoToColumn: "列へ移動",
|
shortcutGoToColumn: "列へ移動",
|
||||||
|
|||||||
@@ -5912,6 +5912,7 @@ export default withEnglishFallback({
|
|||||||
shortcutUndo: "실행 취소",
|
shortcutUndo: "실행 취소",
|
||||||
shortcutRedo: "다시 실행",
|
shortcutRedo: "다시 실행",
|
||||||
shortcutSelectAll: "모두 선택",
|
shortcutSelectAll: "모두 선택",
|
||||||
|
shortcutEditTableStructure: "테이블 구조 편집",
|
||||||
shortcutCopyCurrentRow: "현재 데이터 행 복사",
|
shortcutCopyCurrentRow: "현재 데이터 행 복사",
|
||||||
shortcutDeleteCurrentRow: "현재 데이터 행 삭제",
|
shortcutDeleteCurrentRow: "현재 데이터 행 삭제",
|
||||||
shortcutGoToColumn: "컬럼으로 이동",
|
shortcutGoToColumn: "컬럼으로 이동",
|
||||||
|
|||||||
@@ -6203,6 +6203,7 @@ export default withEnglishFallback({
|
|||||||
shortcutRedo: "Refazer",
|
shortcutRedo: "Refazer",
|
||||||
shortcutSelectAll: "Selecionar tudo",
|
shortcutSelectAll: "Selecionar tudo",
|
||||||
shortcutExtendSelection: "Expandir seleção",
|
shortcutExtendSelection: "Expandir seleção",
|
||||||
|
shortcutEditTableStructure: "Editar estrutura da tabela",
|
||||||
shortcutCopyCurrentRow: "Copiar linha de dados atual",
|
shortcutCopyCurrentRow: "Copiar linha de dados atual",
|
||||||
shortcutDeleteCurrentRow: "Excluir linha de dados atual",
|
shortcutDeleteCurrentRow: "Excluir linha de dados atual",
|
||||||
shortcutGoToColumn: "Ir para a coluna",
|
shortcutGoToColumn: "Ir para a coluna",
|
||||||
|
|||||||
@@ -6516,6 +6516,7 @@ export default withEnglishFallback({
|
|||||||
shortcutUppercaseSelection: "选中内容转为大写",
|
shortcutUppercaseSelection: "选中内容转为大写",
|
||||||
shortcutLowercaseSelection: "选中内容转为小写",
|
shortcutLowercaseSelection: "选中内容转为小写",
|
||||||
shortcutExPasteSqlInCondition: "ExPaste:粘贴为 IN 条件",
|
shortcutExPasteSqlInCondition: "ExPaste:粘贴为 IN 条件",
|
||||||
|
shortcutEditTableStructure: "编辑表结构",
|
||||||
shortcutCopyCurrentRow: "复制当前数据行",
|
shortcutCopyCurrentRow: "复制当前数据行",
|
||||||
shortcutDeleteCurrentRow: "删除当前数据行",
|
shortcutDeleteCurrentRow: "删除当前数据行",
|
||||||
shortcutGoToColumn: "跳转列",
|
shortcutGoToColumn: "跳转列",
|
||||||
|
|||||||
@@ -5508,6 +5508,7 @@ export default withEnglishFallback({
|
|||||||
shortcutRedo: "重做",
|
shortcutRedo: "重做",
|
||||||
shortcutSelectAll: "全選",
|
shortcutSelectAll: "全選",
|
||||||
shortcutExtendSelection: "擴展選取範圍",
|
shortcutExtendSelection: "擴展選取範圍",
|
||||||
|
shortcutEditTableStructure: "編輯資料表結構",
|
||||||
shortcutCopyCurrentRow: "複製目前資料列",
|
shortcutCopyCurrentRow: "複製目前資料列",
|
||||||
shortcutDeleteCurrentRow: "刪除目前資料列",
|
shortcutDeleteCurrentRow: "刪除目前資料列",
|
||||||
shortcutGoToColumn: "跳轉欄",
|
shortcutGoToColumn: "跳轉欄",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
import {
|
import {
|
||||||
eventToModifierOnlyShortcut,
|
eventToModifierOnlyShortcut,
|
||||||
eventToShortcut,
|
eventToShortcut,
|
||||||
|
isEditTableStructureShortcut,
|
||||||
isExecuteSqlInNewResultTabShortcut,
|
isExecuteSqlInNewResultTabShortcut,
|
||||||
isGoToColumnShortcut,
|
isGoToColumnShortcut,
|
||||||
isGoToFirstPageShortcut,
|
isGoToFirstPageShortcut,
|
||||||
@@ -107,6 +108,19 @@ describe("keyboard shortcut matching", () => {
|
|||||||
expect(isGoToColumnShortcut({ key: "g", ctrlKey: true, isComposing: true }, { goToColumn: "Mod+G" })).toBe(false);
|
expect(isGoToColumnShortcut({ key: "g", ctrlKey: true, isComposing: true }, { goToColumn: "Mod+G" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("matches the edit-table-structure shortcut on Windows and macOS", () => {
|
||||||
|
expect(isEditTableStructureShortcut({ key: "d", ctrlKey: true }, undefined, "Win32")).toBe(true);
|
||||||
|
expect(isEditTableStructureShortcut({ key: "d", metaKey: true }, undefined, "MacIntel")).toBe(true);
|
||||||
|
expect(isEditTableStructureShortcut({ key: "d", ctrlKey: true }, undefined, "MacIntel")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("honors custom and disabled edit-table-structure shortcuts", () => {
|
||||||
|
expect(isEditTableStructureShortcut({ key: "e", ctrlKey: true, shiftKey: true }, { editTableStructure: "Shift+Mod+E" }, "Win32")).toBe(true);
|
||||||
|
expect(isEditTableStructureShortcut({ key: "d", ctrlKey: true }, { editTableStructure: "Shift+Mod+E" }, "Win32")).toBe(false);
|
||||||
|
expect(isEditTableStructureShortcut({ key: "d", ctrlKey: true }, { editTableStructure: "" }, "Win32")).toBe(false);
|
||||||
|
expect(isEditTableStructureShortcut({ key: "d", ctrlKey: true, isComposing: true }, undefined, "Win32")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
["goToFirstPage", isGoToFirstPageShortcut, "F1"],
|
["goToFirstPage", isGoToFirstPageShortcut, "F1"],
|
||||||
["goToPreviousPage", isGoToPreviousPageShortcut, "F2"],
|
["goToPreviousPage", isGoToPreviousPageShortcut, "F2"],
|
||||||
|
|||||||
@@ -83,10 +83,32 @@ describe("shortcutRegistry editor actions", () => {
|
|||||||
it("detects go-to-column conflicts only within the grid scope", () => {
|
it("detects go-to-column conflicts only within the grid scope", () => {
|
||||||
const shortcuts = normalizeShortcutSettings({ goToColumn: "Mod+D" });
|
const shortcuts = normalizeShortcutSettings({ goToColumn: "Mod+D" });
|
||||||
|
|
||||||
expect(findShortcutConflict("goToColumn", shortcuts.goToColumn, shortcuts)).toBe("copyCurrentRow");
|
expect(findShortcutConflict("goToColumn", shortcuts.goToColumn, shortcuts)).toBe("editTableStructure");
|
||||||
expect(findShortcutConflict("goToColumn", "Mod+F", shortcuts)).toBeNull();
|
expect(findShortcutConflict("goToColumn", "Mod+F", shortcuts)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("registers edit table structure as the conflict-free default Mod+D grid action", () => {
|
||||||
|
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "editTableStructure");
|
||||||
|
|
||||||
|
expect(definition).toMatchObject({
|
||||||
|
labelKey: "settings.shortcutEditTableStructure",
|
||||||
|
scope: "grid",
|
||||||
|
defaultShortcut: "Mod+D",
|
||||||
|
});
|
||||||
|
expect(DEFAULT_SHORTCUT_SETTINGS.editTableStructure).toBe("Mod+D");
|
||||||
|
expect(DEFAULT_SHORTCUT_SETTINGS.copyCurrentRow).toBe("");
|
||||||
|
expect(findShortcutConflict("editTableStructure", DEFAULT_SHORTCUT_SETTINGS.editTableStructure, DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
|
||||||
|
expect(findShortcutConflict("duplicateLine", DEFAULT_SHORTCUT_SETTINGS.duplicateLine, DEFAULT_SHORTCUT_SETTINGS)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("migrates the legacy copy-row Mod+D default without overwriting explicit shortcuts", () => {
|
||||||
|
expect(normalizeShortcutSettings()).toMatchObject({ editTableStructure: "Mod+D", copyCurrentRow: "" });
|
||||||
|
expect(normalizeShortcutSettings({ copyCurrentRow: "Mod+D" })).toMatchObject({ editTableStructure: "Mod+D", copyCurrentRow: "" });
|
||||||
|
expect(normalizeShortcutSettings({ copyCurrentRow: "Shift+Mod+C" })).toMatchObject({ editTableStructure: "Mod+D", copyCurrentRow: "Shift+Mod+C" });
|
||||||
|
expect(normalizeShortcutSettings({ editTableStructure: "", copyCurrentRow: "Mod+D" })).toMatchObject({ editTableStructure: "", copyCurrentRow: "Mod+D" });
|
||||||
|
expect(normalizeShortcutSettings({ editTableStructure: "Shift+Mod+D", copyCurrentRow: "Mod+D" })).toMatchObject({ editTableStructure: "Shift+Mod+D", copyCurrentRow: "Mod+D" });
|
||||||
|
});
|
||||||
|
|
||||||
it("registers the new-data-tab mouse modifier as a configurable sidebar shortcut", () => {
|
it("registers the new-data-tab mouse modifier as a configurable sidebar shortcut", () => {
|
||||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "openDataInNewTab");
|
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "openDataInNewTab");
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,10 @@ export function isCopyCurrentRowShortcut(event: ShortcutLikeEvent, shortcuts?: P
|
|||||||
return matchesShortcut(event, actionShortcut("copyCurrentRow", shortcuts));
|
return matchesShortcut(event, actionShortcut("copyCurrentRow", shortcuts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isEditTableStructureShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>, platform = globalThis.navigator?.platform || ""): boolean {
|
||||||
|
return matchesShortcut(event, actionShortcut("editTableStructure", shortcuts, platform), platform);
|
||||||
|
}
|
||||||
|
|
||||||
export function isDeleteCurrentRowShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
export function isDeleteCurrentRowShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||||
return matchesShortcut(event, actionShortcut("deleteCurrentRow", shortcuts));
|
return matchesShortcut(event, actionShortcut("deleteCurrentRow", shortcuts));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type ShortcutActionId =
|
|||||||
| "lowercaseSelection"
|
| "lowercaseSelection"
|
||||||
| "exPasteSqlInCondition"
|
| "exPasteSqlInCondition"
|
||||||
| "toggleFold"
|
| "toggleFold"
|
||||||
|
| "editTableStructure"
|
||||||
| "copyCurrentRow"
|
| "copyCurrentRow"
|
||||||
| "deleteCurrentRow"
|
| "deleteCurrentRow"
|
||||||
| "goToColumn"
|
| "goToColumn"
|
||||||
@@ -108,6 +109,7 @@ const PLATFORM_DEFAULT_SHORTCUTS: Partial<Record<ShortcutActionId, ReadonlySet<s
|
|||||||
navigateTabHistoryForward: new Set(["Ctrl+Alt+ArrowRight", "Mod+Alt+ArrowRight"]),
|
navigateTabHistoryForward: new Set(["Ctrl+Alt+ArrowRight", "Mod+Alt+ArrowRight"]),
|
||||||
};
|
};
|
||||||
const LEGACY_CLOSE_TAB_DEFAULT = "Meta+W";
|
const LEGACY_CLOSE_TAB_DEFAULT = "Meta+W";
|
||||||
|
const LEGACY_COPY_CURRENT_ROW_DEFAULT = "Mod+D";
|
||||||
const TAB_NAVIGATION_HISTORY_ACTIONS: ShortcutActionId[] = ["navigateTabHistoryBack", "navigateTabHistoryForward"];
|
const TAB_NAVIGATION_HISTORY_ACTIONS: ShortcutActionId[] = ["navigateTabHistoryBack", "navigateTabHistoryForward"];
|
||||||
|
|
||||||
export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
||||||
@@ -273,11 +275,17 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
|||||||
scope: "editor",
|
scope: "editor",
|
||||||
defaultShortcut: "Mod+.",
|
defaultShortcut: "Mod+.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "editTableStructure",
|
||||||
|
labelKey: "settings.shortcutEditTableStructure",
|
||||||
|
scope: "grid",
|
||||||
|
defaultShortcut: "Mod+D",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "copyCurrentRow",
|
id: "copyCurrentRow",
|
||||||
labelKey: "settings.shortcutCopyCurrentRow",
|
labelKey: "settings.shortcutCopyCurrentRow",
|
||||||
scope: "grid",
|
scope: "grid",
|
||||||
defaultShortcut: "Mod+D",
|
defaultShortcut: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "deleteCurrentRow",
|
id: "deleteCurrentRow",
|
||||||
@@ -591,6 +599,11 @@ export function normalizeShortcutSettings(settings?: Partial<ShortcutSettings>,
|
|||||||
}),
|
}),
|
||||||
) as ShortcutSettings;
|
) as ShortcutSettings;
|
||||||
|
|
||||||
|
if (!hasExplicitShortcut(settings, "editTableStructure") && settings?.copyCurrentRow === LEGACY_COPY_CURRENT_ROW_DEFAULT) {
|
||||||
|
normalized.editTableStructure = LEGACY_COPY_CURRENT_ROW_DEFAULT;
|
||||||
|
normalized.copyCurrentRow = "";
|
||||||
|
}
|
||||||
|
|
||||||
for (const actionId of TAB_NAVIGATION_HISTORY_ACTIONS) {
|
for (const actionId of TAB_NAVIGATION_HISTORY_ACTIONS) {
|
||||||
if (hasExplicitShortcut(settings, actionId)) continue;
|
if (hasExplicitShortcut(settings, actionId)) continue;
|
||||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === actionId);
|
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === actionId);
|
||||||
|
|||||||
Reference in new issue
Block a user