feat(tabs): add adjacent data tab opening option

Closes #3723
This commit is contained in:
t8y2 committed 2026-08-15 00:52:24 +00:00
1 parent fbaf556f3a
commit 4768dbb99a
19 files changed
+192 -7

No files matched your search

@@ -59,6 +59,28 @@ describe("queryStore switchTab", () => {
expect(queryStore.activeTabId).toBe(tab2Id);
});
it("inserts a tab after the active tab only when explicitly requested", async () => {
const queryStore = useQueryStore();
const firstId = queryStore.createTab("pg-1", "app", "Query 1", "query");
const trailingId = queryStore.createTab("pg-1", "app", "Query 2", "query");
queryStore.switchTab(firstId);
const dataId = queryStore.createTab("pg-1", "app", "users", "data", "public", undefined, undefined, { forceNew: true, insertAfterActive: true });
const appendedId = queryStore.createTab("pg-1", "app", "Query 3", "query", undefined, undefined, undefined, { forceNew: true });
expect(queryStore.tabs.map((tab) => tab.id)).toEqual([firstId, dataId, trailingId, appendedId]);
});
it("appends when adjacent insertion has no valid active tab", async () => {
const queryStore = useQueryStore();
const firstId = queryStore.createTab("pg-1", "app", "Query 1", "query");
queryStore.activeTabId = "missing-tab";
const dataId = queryStore.createTab("pg-1", "app", "users", "data", "public", undefined, undefined, { forceNew: true, insertAfterActive: true });
expect(queryStore.tabs.map((tab) => tab.id)).toEqual([firstId, dataId]);
});
it("deactivates settings page when reopening an existing special tab", async () => {
const { useQueryStore } = await import("@/stores/queryStore");
const { useSettingsStore } = await import("@/stores/settingsStore");
+25
View File
@@ -28,6 +28,14 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ reuseDataTab: true } as any).dataTabReuseMode).toBe("same-table");
});
it("keeps adjacent data-tab opening disabled unless explicitly enabled", () => {
expect(normalizeEditorSettings({}).openDataTabsNextToActive).toBe(false);
expect(normalizeEditorSettings({ openDataTabsNextToActive: true }).openDataTabsNextToActive).toBe(true);
expect(normalizeEditorSettings({ openDataTabsNextToActive: false }).openDataTabsNextToActive).toBe(false);
expect(normalizeEditorSettings({ openDataTabsNextToActive: "true" } as any).openDataTabsNextToActive).toBe(false);
expect(normalizeEditorSettings({ openDataTabsNextToActive: null } as any).openDataTabsNextToActive).toBe(false);
});
it("defaults and bounds the regular expression match limit", () => {
expect(normalizeEditorSettings({}).regexMaxMatchCount).toBe(1000);
expect(normalizeEditorSettings({ regexMaxMatchCount: 2500 }).regexMaxMatchCount).toBe(2500);
@@ -663,6 +671,23 @@ describe("settingsStore persisted settings initialization", () => {
);
});
it("loads and persists adjacent data-tab opening", async () => {
const loadEditorSettings = vi.fn().mockResolvedValue({ openDataTabsNextToActive: true });
const saveEditorSettings = vi.fn().mockResolvedValue(undefined);
vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings }));
const { useSettingsStore } = await import("@/stores/settingsStore");
const store = useSettingsStore();
await store.initEditorSettings();
expect(store.editorSettings.openDataTabsNextToActive).toBe(true);
await store.updateEditorSettingsAndPersist({ openDataTabsNextToActive: false });
expect(store.editorSettings.openDataTabsNextToActive).toBe(false);
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ openDataTabsNextToActive: false }));
});
it("shares concurrent initialization and applies startup changes after saved settings load", async () => {
let resolveLoad!: (value: unknown) => void;
const loadEditorSettings = vi.fn(
+4 -2
View File
@@ -1620,7 +1620,7 @@ export const useQueryStore = defineStore("query", () => {
return tabs.value.find((tab) => tab.connectionId === connectionId && tab.database === database && tab.title === title && tab.mode === mode && (tab.schema || "") === (schema || "") && (tab.catalog || "") === (catalog || ""));
}
function createTab(connectionId: string, database: string, title?: string, mode: QueryTab["mode"] = "query", schema?: string, initialSql?: string, catalog?: string, options: { forceNew?: boolean; activate?: boolean; forceWordWrap?: boolean } = {}) {
function createTab(connectionId: string, database: string, title?: string, mode: QueryTab["mode"] = "query", schema?: string, initialSql?: string, catalog?: string, options: { forceNew?: boolean; activate?: boolean; forceWordWrap?: boolean; insertAfterActive?: boolean } = {}) {
if (title && !options.forceNew) {
const existing = findTabByIdentity(connectionId, database, title, mode, schema, catalog);
if (existing) {
@@ -1646,7 +1646,9 @@ export const useQueryStore = defineStore("query", () => {
mode,
};
if (mode === "query") tab.originalSql = initialSql ?? "";
tabs.value.push(tab);
const activeIndex = options.insertAfterActive ? tabs.value.findIndex((item) => item.id === activeTabId.value) : -1;
if (activeIndex >= 0) tabs.value.splice(activeIndex + 1, 0, tab);
else tabs.value.push(tab);
if (options.activate !== false) activeTabId.value = id;
return id;
}
+4
View File
@@ -580,6 +580,7 @@ export interface EditorSettings {
openTabsRestoreMode: OpenTabsRestoreMode;
disconnectTabHandlingMode: DisconnectTabHandlingMode;
dataTabReuseMode: DataTabReuseMode;
openDataTabsNextToActive: boolean;
prefillNewQueryWithSelect: boolean;
updateNotificationsEnabled: boolean;
sidebarHiddenTablePrefixes: string[];
@@ -778,6 +779,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
openTabsRestoreMode: "all",
disconnectTabHandlingMode: "close-tabs",
dataTabReuseMode: DEFAULT_DATA_TAB_REUSE_MODE,
openDataTabsNextToActive: false,
prefillNewQueryWithSelect: true,
updateNotificationsEnabled: true,
sidebarHiddenTablePrefixes: [],
@@ -1142,6 +1144,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
}
).reuseDataTab,
),
openDataTabsNextToActive: typeof settings.openDataTabsNextToActive === "boolean" ? settings.openDataTabsNextToActive : DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive,
prefillNewQueryWithSelect: typeof settings.prefillNewQueryWithSelect === "boolean" ? settings.prefillNewQueryWithSelect : DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect,
updateNotificationsEnabled: settings.updateNotificationsEnabled ?? DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled,
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes),
@@ -1702,6 +1705,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.openTabsRestoreMode !== undefined) editorSettings.value.openTabsRestoreMode = normalizeOpenTabsRestoreMode(partial.openTabsRestoreMode);
if (partial.disconnectTabHandlingMode !== undefined) editorSettings.value.disconnectTabHandlingMode = normalizeDisconnectTabHandlingMode(partial.disconnectTabHandlingMode);
if (partial.dataTabReuseMode !== undefined) editorSettings.value.dataTabReuseMode = normalizeDataTabReuseMode(partial.dataTabReuseMode);
if (partial.openDataTabsNextToActive !== undefined) editorSettings.value.openDataTabsNextToActive = partial.openDataTabsNextToActive === true;
if (partial.prefillNewQueryWithSelect !== undefined) editorSettings.value.prefillNewQueryWithSelect = partial.prefillNewQueryWithSelect;
if (partial.updateNotificationsEnabled !== undefined) editorSettings.value.updateNotificationsEnabled = partial.updateNotificationsEnabled;
if (partial.sidebarHiddenTablePrefixes !== undefined) editorSettings.value.sidebarHiddenTablePrefixes = normalizeSidebarHiddenTablePrefixes(partial.sidebarHiddenTablePrefixes);