From a4e4fd0d2f8baa6c6e66a9b6bf5dc2607ee90acc Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 9 Sep 2026 10:27:03 +0800 Subject: [PATCH] fix(mongodb): preserve collection queries and preview selection Closes #8306 --- apps/desktop/src/components/grid/DataGrid.vue | 7 +++- .../DataGridCellDetailSelection.spec.ts | 7 ++++ .../sidebar/SidebarTreeRuntimeHost.vue | 7 ++++ .../SidebarTreeRuntimeHost.expansion.spec.ts | 35 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index ae7bc1fb23..fc6f08e686 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -5534,7 +5534,12 @@ function closeCellDetails() { function toggleMongoJsonPreview() { if (!canShowMongoJsonPreview.value) return; showMongoJsonPreview.value = !showMongoJsonPreview.value; - if (showMongoJsonPreview.value) closeCellDetails(); + if (showMongoJsonPreview.value) { + closeCellDetails(); + if (!currentSelectedCellPosition() && displayItems.value.length > 0 && visibleColumnIndexes.value.length > 0) { + selectSingleCell(0, 0); + } + } } function closeMongoJsonPreview() { diff --git a/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts index b307691fc0..e33ef81d27 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts @@ -5,6 +5,13 @@ const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), const largeValueSource = readFileSync(new URL("../../../composables/useDataGridLargeValues.ts", import.meta.url), "utf8"); describe("DataGrid cell detail selection", () => { + it("selects the first available cell when opening Mongo JSON preview without a selection", () => { + const togglePreview = dataGridSource.match(/function toggleMongoJsonPreview[\s\S]*?\n\}/)?.[0]; + expect(togglePreview).toContain("if (showMongoJsonPreview.value)"); + expect(togglePreview).toContain("!currentSelectedCellPosition() && displayItems.value.length > 0 && visibleColumnIndexes.value.length > 0"); + expect(togglePreview).toContain("selectSingleCell(0, 0)"); + }); + it("keeps Canvas hover state while the renderer swaps drawing surfaces", () => { expect(dataGridSource).toContain("function isCanvasGridInteractionTarget(target: Node): boolean"); expect(dataGridSource).toContain("canvasOverlayRef.value?.contains(target) === true"); diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 35a47eb391..7943ab671c 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -1533,6 +1533,13 @@ function openMongoTreeData(node: TreeNode) { return; } if (node.type !== "mongo-collection") return; + const existing = queryStore.tabs.find( + (tab) => tab.mode === "mongo" && tab.connectionId === node.connectionId && tab.database === node.database && tab.tableMeta?.tableName === node.label, + ); + if (existing) { + queryStore.switchTab(existing.id); + return; + } const tab = queryStore.createTab(node.connectionId, node.database, tabTitle, "mongo"); queryStore.updateSql(tab, node.label); queryStore.setTableMeta(tab, { diff --git a/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts b/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts index f8257afe69..a23faa043a 100644 --- a/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts +++ b/apps/desktop/src/components/sidebar/__tests__/SidebarTreeRuntimeHost.expansion.spec.ts @@ -30,7 +30,11 @@ const connectionStore = { }; const queryStore = { + tabs: [] as Array<{ id: string; mode: string; connectionId: string; database: string; title?: string; tableMeta?: { tableName: string } }>, createTab: vi.fn(), + switchTab: vi.fn(), + updateSql: vi.fn(), + setTableMeta: vi.fn(), openNacosAdmin: vi.fn(), }; @@ -61,6 +65,7 @@ afterEach(() => { connectionStore.sidebarSearchQuery = ""; connectionStore.activeConnectionId = null; connectionStore.connectedIds.clear(); + queryStore.tabs = []; vi.clearAllMocks(); connectionStore.canUseLoadedTreeNodeToggle.mockReturnValue(true); connectionStore.getConfig.mockReturnValue({ db_type: "mysql", name: "connection" }); @@ -68,6 +73,36 @@ afterEach(() => { }); describe("SidebarTreeRuntimeHost expansion", () => { + it("reuses a Mongo collection tab by identity without replacing its state", async () => { + const collection: TreeNode = { + id: "mongo:app:orders", + label: "orders", + type: "mongo-collection", + connectionId: "mongo", + database: "app", + }; + connectionStore.getConfig.mockReturnValue({ db_type: "mongodb", name: "connection" }); + queryStore.tabs = [ + { id: "other-database", mode: "mongo", connectionId: "mongo", database: "other", tableMeta: { tableName: "orders" } }, + { id: "orders-tab", mode: "mongo", connectionId: "mongo", database: "app", title: "My renamed collection", tableMeta: { tableName: "orders" } }, + ]; + const host = ref | null>(null); + const app = createApp(defineComponent({ setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: collection, depth: 0 }) })); + mountedApps.push(app); + const container = document.createElement("div"); + document.body.append(container); + app.use(i18n); + app.mount(container); + + host.value?.handleRowClick(collection, 1); + await nextTick(); + + expect(queryStore.switchTab).toHaveBeenCalledWith("orders-tab"); + expect(queryStore.createTab).not.toHaveBeenCalled(); + expect(queryStore.updateSql).not.toHaveBeenCalled(); + expect(queryStore.setTableMeta).not.toHaveBeenCalled(); + }); + it("activates the owning connection when a cached node toggles locally", async () => { const group: TreeNode = { id: "mysql:basic:__tables",