fix(tabs): reuse data tabs only for the same table

This commit is contained in:
zipg authored and GitHub committed 2026-08-03 17:05:56 +08:00
1 parent 6e4363be36
commit 51560c49ae
16 files changed
+115 -23

No files matched your search

@@ -99,6 +99,7 @@ describe("useNavigationTargets with the real query store", () => {
vi.unstubAllGlobals();
installLocalStorage();
mocks.connectionStore.activeConnectionId = "";
mocks.connectionStore.getConfig.mockImplementation((connectionId: string) => ({ id: connectionId, db_type: "postgres" }));
mocks.settingsStore.editorSettings.reuseDataTab = true;
mocks.ensureConnected?.mockResolvedValue?.(undefined);
mocks.connectionStore.ensureConnected.mockResolvedValue(undefined);
@@ -172,6 +173,63 @@ describe("useNavigationTargets with the real query store", () => {
expect(new Set(queryStore.tabs.map((tab) => tab.id))).toHaveLength(2);
});
it("keeps different sidebar tables independent when data-tab reuse is enabled", async () => {
const { queryStore } = await setupNavigation();
const { useSidebarDataOpenRuntime } = await import("@/composables/useSidebarDataOpenRuntime");
const runtime = useSidebarDataOpenRuntime();
const users = { id: "users", label: "users", type: "table" as const, connectionId: "connection-1", database: "app", schema: "public", tableType: "TABLE" };
await runtime.openData(users);
await runtime.openData({ ...users, id: "orders", label: "orders" });
expect(queryStore.tabs).toHaveLength(2);
expect(queryStore.tabs.map((tab) => tab.tableMeta?.tableName)).toEqual(["users", "orders"]);
});
it("reuses a sidebar table when the same table is opened from the object browser", async () => {
mocks.connectionStore.getConfig.mockImplementation((connectionId: string) => ({ id: connectionId, db_type: "mysql" }));
const { navigation, queryStore } = await setupNavigation();
const { useSidebarDataOpenRuntime } = await import("@/composables/useSidebarDataOpenRuntime");
const runtime = useSidebarDataOpenRuntime();
const users = { id: "users", label: "users", type: "table" as const, connectionId: "connection-1", database: "app", tableType: "TABLE" };
await runtime.openData(users);
const sidebarTabId = queryStore.activeTabId;
await navigation.openObjectBrowserTableTarget({ connectionId: "connection-1", database: "app", schema: "app", tableName: "users", tableType: "TABLE" });
expect(queryStore.tabs).toHaveLength(1);
expect(queryStore.activeTabId).toBe(sidebarTabId);
});
it("reuses a restored legacy MySQL tab when the same table is opened from the sidebar", async () => {
mocks.connectionStore.getConfig.mockImplementation((connectionId: string) => ({ id: connectionId, db_type: "mysql" }));
mocks.loadOpenTabsState.mockResolvedValue({
tabs: [
{
id: "restored-users",
title: "app.users",
connectionId: "connection-1",
database: "app",
schema: "app",
mode: "data",
sql: "SELECT * FROM users",
tableMeta: { schema: "app", tableName: "users", tableType: "TABLE", columns: [], primaryKeys: [] },
},
],
activeTabId: "restored-users",
});
const { queryStore } = await setupNavigation();
await queryStore.initOpenTabs({ validConnectionIds: ["connection-1"] });
const { useSidebarDataOpenRuntime } = await import("@/composables/useSidebarDataOpenRuntime");
const runtime = useSidebarDataOpenRuntime();
await runtime.openData({ id: "users", label: "users", type: "table", connectionId: "connection-1", database: "app", tableType: "TABLE" });
expect(queryStore.tabs).toHaveLength(1);
expect(queryStore.activeTabId).toBe("restored-users");
expect(queryStore.tabs[0]?.schema).toBeUndefined();
});
it("creates a new target tab even when the same table was restored", async () => {
mocks.loadOpenTabsState.mockResolvedValue({
tabs: [
@@ -169,6 +169,17 @@ describe("useSidebarDataOpenRuntime", () => {
expect(mocks.tabs).toHaveLength(1);
});
it("keeps different sidebar tables independent when reuse is enabled", async () => {
mocks.reuseDataTab = true;
const ordersNode = { ...tableNode, id: "table-orders", label: "orders" };
await useSidebarDataOpenRuntime().openData(tableNode);
await useSidebarDataOpenRuntime().openData(ordersNode);
expect(mocks.tabs).toHaveLength(2);
expect(mocks.tabs.map((tab) => tab.title)).toEqual(["users", "orders"]);
});
it("creates a new HBase tab for the same table when reuse is disabled", async () => {
mocks.databaseType = "hbase";
@@ -178,6 +189,16 @@ describe("useSidebarDataOpenRuntime", () => {
expect(mocks.tabs).toHaveLength(2);
});
it("keeps different HBase tables independent when reuse is enabled", async () => {
mocks.databaseType = "hbase";
mocks.reuseDataTab = true;
await useSidebarDataOpenRuntime().openData(tableNode);
await useSidebarDataOpenRuntime().openData({ ...tableNode, id: "table-orders", label: "orders" });
expect(mocks.tabs).toHaveLength(2);
});
it("starts cold-cache OceanBase metadata before the table query", async () => {
await useSidebarDataOpenRuntime().openData(tableNode);
@@ -30,7 +30,7 @@ export function useSidebarDataOpenRuntime() {
async function openData(node: TreeNode, request?: SidebarDataOpenRequest, openMode: DataTabOpenMode = "default", options: { reuseScope?: DataTabReuseScope } = {}) {
if (!(node.type === "table" || node.type === "view" || node.type === "materialized_view") || !hasNodeDatabaseContext(node)) return;
const config = connectionStore.getConfig(node.connectionId);
const reuseScope = options.reuseScope ?? (settingsStore.editorSettings.reuseDataTab ? "database" : "none");
const reuseScope = options.reuseScope ?? (settingsStore.editorSettings.reuseDataTab ? "same-table" : "none");
if (config?.db_type === "hbase") {
await connectionStore.ensureConnected(node.connectionId);
const tabId = queryStore.createTab(node.connectionId, node.database, node.label, "hbase", undefined, node.label, undefined, { forceNew: openMode === "new-tab" || reuseScope === "none" });