From 2db46f10ff38588d0e4adf6915db61c22ce3947e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Mon, 8 Jun 2026 20:10:08 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(connectionStore):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=86=E7=BB=84=E8=BF=9E=E6=8E=A5=E6=97=B6?= =?UTF-8?q?=E6=A0=91=E8=8A=82=E7=82=B9=E7=9A=84=E6=9B=B4=E6=96=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 确保连接到分组中的连接时,原地更新现有节点属性,避免添加重复的根节点。 使用 findNode 递归搜索替代 findIndex 只搜索根级别。原地修改节点属性而非替换整个对象,保留父子引用关系。 --- apps/desktop/src/stores/connectionStore.ts | 26 ++--- .../connectionStoreGroupedConnect.test.ts | 98 +++++++++++++++++++ 2 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 packages/app-tests/connectionStoreGroupedConnect.test.ts diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 0af945852..2b176eb50 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -759,19 +759,21 @@ export const useConnectionStore = defineStore("connection", () => { clearConnectionError(config.id); if (id !== config.id) clearConnectionError(id); - const node: TreeNode = { - id, - label: config.name, - type: "connection", - connectionId: id, - isExpanded: false, - children: [], - }; - const existing = treeNodes.value.findIndex((n) => n.id === id); - if (existing >= 0) { - treeNodes.value[existing] = node; + const existing = findNode(treeNodes.value, id); + if (existing) { + existing.label = config.name; + existing.type = "connection"; + existing.connectionId = id; + existing.children = existing.children || []; } else { - treeNodes.value.push(node); + treeNodes.value.push({ + id, + label: config.name, + type: "connection", + connectionId: id, + isExpanded: false, + children: [], + }); } return id; } catch (e) { diff --git a/packages/app-tests/connectionStoreGroupedConnect.test.ts b/packages/app-tests/connectionStoreGroupedConnect.test.ts new file mode 100644 index 000000000..bc0dd876b --- /dev/null +++ b/packages/app-tests/connectionStoreGroupedConnect.test.ts @@ -0,0 +1,98 @@ +import { test } from "vitest"; +import assert from "node:assert/strict"; +import { createPinia, setActivePinia } from "pinia"; +import { useConnectionStore } from "../../apps/desktop/src/stores/connectionStore.ts"; +import type { ConnectionConfig, SidebarLayout, TreeNode } from "../../apps/desktop/src/types/database.ts"; + +function installMemoryStorage() { + const values = new Map(); + const original = Object.getOwnPropertyDescriptor(globalThis, "localStorage"); + Object.defineProperty(globalThis, "localStorage", { + configurable: true, + value: { + getItem: (key: string) => values.get(key) ?? null, + setItem: (key: string, value: string) => values.set(key, value), + removeItem: (key: string) => values.delete(key), + clear: () => values.clear(), + }, + }); + return { + restore() { + if (original) Object.defineProperty(globalThis, "localStorage", original); + else Reflect.deleteProperty(globalThis, "localStorage"); + }, + }; +} + +function conn(id: string, name: string): ConnectionConfig { + return { + id, + name, + db_type: "mysql", + host: "127.0.0.1", + port: 3306, + username: "root", + password: "secret", + }; +} + +function countConnectionNodes(nodes: TreeNode[], connectionId: string): number { + let count = 0; + for (const node of nodes) { + if (node.type === "connection" && node.connectionId === connectionId) count++; + if (node.children) count += countConnectionNodes(node.children, connectionId); + } + return count; +} + +test("connecting a grouped connection updates it in place instead of adding a root node", async () => { + const originalFetch = globalThis.fetch; + const storage = installMemoryStorage(); + const layout: SidebarLayout = { + groups: [{ id: "group-1", name: "Group", collapsed: false }], + order: [{ type: "group", id: "group-1", connectionIds: [] }], + }; + + globalThis.fetch = (async (input, init) => { + const url = String(input); + if (url === "/api/connection/list") { + return new Response("[]", { status: 200, headers: { "Content-Type": "application/json" } }); + } + if (url === "/api/layout/sidebar") { + if (init?.method === "POST") { + return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } }); + } + return new Response(JSON.stringify(layout), { status: 200, headers: { "Content-Type": "application/json" } }); + } + if (url === "/api/connection/save") { + return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } }); + } + if (url === "/api/connection/connect") { + const body = JSON.parse(String(init?.body ?? "{}")); + return new Response(JSON.stringify(body.config.id), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } }); + }) as typeof fetch; + + try { + setActivePinia(createPinia()); + const store = useConnectionStore(); + await store.initFromDisk(); + store.startCreatingConnectionInGroup("group-1"); + + const config = conn("conn-1", "Grouped MySQL"); + await store.addConnection(config); + await store.connect(config); + + assert.equal(store.treeNodes.length, 1); + assert.equal(store.treeNodes[0].type, "connection-group"); + assert.deepEqual(store.treeNodes[0].children?.map((node) => node.id), ["conn-1"]); + assert.equal(countConnectionNodes(store.treeNodes, "conn-1"), 1); + } finally { + globalThis.fetch = originalFetch; + storage.restore(); + } +});