fix(tabs): honor data tab reuse across activation paths

This commit is contained in:
zipg authored and GitHub committed 2026-07-31 14:25:21 +08:00
1 parent 9b8f3956b6
commit dd383ff3ea
18 files changed
+157 -34

No files matched your search

@@ -52,18 +52,19 @@ describe("dataTabOpenPolicy", () => {
const existing = dataTab("users", "users");
existing.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] };
expect(findExistingDataTabCandidate([existing], usersTarget, { openMode: "new-tab", reuseDataTab: true })).toBeUndefined();
expect(findExistingDataTabCandidate([existing], usersTarget, { openMode: "new-tab", reuseDataTab: false })).toBeUndefined();
expect(findExistingDataTabCandidate([existing], usersTarget, { openMode: "new-tab", reuseScope: "database" })).toBeUndefined();
expect(findExistingDataTabCandidate([existing], usersTarget, { openMode: "new-tab", reuseScope: "none" })).toBeUndefined();
});
it("preserves same-table activation and configured database-tab reuse for ordinary opens", () => {
it("applies none, same-table, and database reuse scopes independently", () => {
const sameTable = dataTab("users", "users");
sameTable.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] };
const otherTable = dataTab("orders", "orders");
expect(findExistingDataTabCandidate([sameTable], usersTarget, { openMode: "default", reuseDataTab: false })).toEqual({ tab: sameTable, match: "same-table" });
expect(findExistingDataTabCandidate([otherTable], usersTarget, { openMode: "default", reuseDataTab: true })).toEqual({ tab: otherTable, match: "database" });
expect(findExistingDataTabCandidate([otherTable], usersTarget, { openMode: "default", reuseDataTab: false })).toBeUndefined();
expect(findExistingDataTabCandidate([sameTable], usersTarget, { openMode: "default", reuseScope: "none" })).toBeUndefined();
expect(findExistingDataTabCandidate([sameTable], usersTarget, { openMode: "default", reuseScope: "same-table" })).toEqual({ tab: sameTable, match: "same-table" });
expect(findExistingDataTabCandidate([otherTable], usersTarget, { openMode: "default", reuseScope: "same-table" })).toBeUndefined();
expect(findExistingDataTabCandidate([otherTable], usersTarget, { openMode: "default", reuseScope: "database" })).toEqual({ tab: otherTable, match: "database" });
});
it("allows metadata to update a tab that still points to the requested table", () => {
@@ -79,7 +80,7 @@ describe("dataTabOpenPolicy", () => {
tab.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] };
expect(canApplyDataTabMetadata(tab, usersTarget, new AbortController().signal)).toBe(true);
expect(findExistingDataTabCandidate([tab], usersTarget, { openMode: "default", reuseDataTab: false })).toEqual({ tab, match: "same-table" });
expect(findExistingDataTabCandidate([tab], usersTarget, { openMode: "default", reuseScope: "same-table" })).toEqual({ tab, match: "same-table" });
});
it("ignores metadata query schemas for database-scoped tables", () => {
@@ -89,7 +90,7 @@ describe("dataTabOpenPolicy", () => {
const mysqlTarget = { connectionId: "conn", database: "app", tableName: "users" };
expect(canApplyDataTabMetadata(tab, mysqlTarget, new AbortController().signal)).toBe(true);
expect(findExistingDataTabCandidate([tab], mysqlTarget, { openMode: "default", reuseDataTab: false })).toEqual({ tab, match: "same-table" });
expect(findExistingDataTabCandidate([tab], mysqlTarget, { openMode: "default", reuseScope: "same-table" })).toEqual({ tab, match: "same-table" });
});
it("rejects metadata after its request is cancelled", () => {
@@ -2,6 +2,7 @@ import { matchesModifierOnlyShortcut, type ShortcutLikeEvent } from "@/lib/edito
import type { QueryTab, TreeNodeType } from "@/types/database";
export type DataTabOpenMode = "default" | "new-tab";
export type DataTabReuseScope = "none" | "same-table" | "database";
type DataTabLike = Pick<QueryTab, "id" | "mode" | "connectionId" | "database" | "schema" | "title" | "tableMeta" | "tableMetaUpdatedAt">;
@@ -47,12 +48,12 @@ export function dataTabMetadataNeedsRefresh(tab: DataTabLike, maxAgeMs: number,
return now - tab.tableMetaUpdatedAt >= maxAgeMs;
}
export function findExistingDataTabCandidate<T extends DataTabLike>(tabs: T[], target: DataTabTarget, options: { openMode: DataTabOpenMode; reuseDataTab: boolean }): ExistingDataTabCandidate<T> | undefined {
if (options.openMode === "new-tab") return undefined;
export function findExistingDataTabCandidate<T extends DataTabLike>(tabs: T[], target: DataTabTarget, options: { openMode: DataTabOpenMode; reuseScope: DataTabReuseScope }): ExistingDataTabCandidate<T> | undefined {
if (options.openMode === "new-tab" || options.reuseScope === "none") return undefined;
const sameTable = tabs.find((tab) => isSameTable(tab, target));
if (sameTable) return { tab: sameTable, match: "same-table" };
if (!options.reuseDataTab) return undefined;
if (options.reuseScope === "same-table") return undefined;
const sameDatabase = tabs.find((tab) => isSameDatabase(tab, target));
return sameDatabase ? { tab: sameDatabase, match: "database" } : undefined;
@@ -12,8 +12,9 @@ export function canActivateExistingDataTableTab(tab: QueryTab, options: { activa
return !!tab.result || !!tab.results?.length;
}
export function dataTableDoubleClickAction(tab: QueryTab | undefined, activation: "single" | "double"): DataTableDoubleClickAction {
export function dataTableDoubleClickAction(tab: QueryTab | undefined, activation: "single" | "double", reuseDataTab = true): DataTableDoubleClickAction {
if (activation === "single") return "none";
if (!reuseDataTab) return "open";
if (!tab) return activation === "double" ? "open" : "none";
if (!canActivateExistingDataTableTab(tab)) return "open";
return "activate";