mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 08:18:52 +00:00
@@ -1567,6 +1567,16 @@ async function locateTabInSidebar(tab: QueryTab | undefined | null, align: Sideb
|
||||
}
|
||||
}
|
||||
|
||||
// Connection groups never register loaded tree children, so the guard above
|
||||
// skips them and a collapsed group keeps the target out of the visible flat
|
||||
// tree. Reopen them through the layout op so the expansion is persisted and
|
||||
// survives the next layout rebuild; flipping isExpanded directly would be
|
||||
// reverted by that rebuild (issue #7387).
|
||||
const collapsedGroupIds = nodePath.filter((node) => node.type === "connection-group" && !node.isExpanded).map((node) => node.id);
|
||||
if (collapsedGroupIds.length > 0) {
|
||||
store.expandConnectionGroups(collapsedGroupIds);
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
|
||||
const match = target ? findSidebarNodeForTarget(target, flatNodes.value) : null;
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const connectionTreeSource = readFileSync(new URL("../ConnectionTree.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("ConnectionTree locate in collapsed groups", () => {
|
||||
it("reopens collapsed connection groups on the node path through the persisted layout op", () => {
|
||||
// Group expansion must flow through store.expandConnectionGroups so
|
||||
// layout.collapsed is flipped and the next layout rebuild keeps the group
|
||||
// open; flipping isExpanded directly would be reverted by that rebuild.
|
||||
expect(connectionTreeSource).toMatch(/const collapsedGroupIds = nodePath\s*\.filter\(\(node\) => node\.type === "connection-group" && !node\.isExpanded\)\s*\.map\(\(node\) => node\.id\);/);
|
||||
// The expansion happens before the flat-tree match that drives selection,
|
||||
// scrolling and flashing, so the reopened groups are visible to it.
|
||||
expect(connectionTreeSource).toMatch(/store\.expandConnectionGroups\(collapsedGroupIds\);\s*\}\s*\n\s*await nextTick\(\);\s*\n\s*const match = target \? findSidebarNodeForTarget\(target, flatNodes\.value\) : null;/);
|
||||
});
|
||||
|
||||
it("still gates generic ancestor expansion on loaded children only (#5850)", () => {
|
||||
expect(connectionTreeSource).toMatch(/if \(!ancestor\.isExpanded && store\.canUseLoadedTreeNodeToggle\(ancestor\)\) \{\s*ancestor\.isExpanded = true;\s*\}/);
|
||||
});
|
||||
});
|
||||
+60
-2
@@ -1,8 +1,10 @@
|
||||
import { strict as assert } from "node:assert";
|
||||
import { test } from "vitest";
|
||||
import { activeTabSidebarTarget, findNodePathForTarget, findSidebarConnectionNode, findSidebarNodeForActiveTab, scrollTopForSidebarNode, shouldScrollActiveSidebarSelection } from "../../apps/desktop/src/lib/sidebar/sidebarActiveTabTarget.ts";
|
||||
import { activeTabSidebarTarget, findNodePathForTarget, findSidebarConnectionNode, findSidebarNodeForActiveTab, findSidebarNodeForTarget, scrollTopForSidebarNode, shouldScrollActiveSidebarSelection } from "../../apps/desktop/src/lib/sidebar/sidebarActiveTabTarget.ts";
|
||||
import { buildTreeNodesFromLayout, expandGroups } from "../../apps/desktop/src/lib/sidebar/sidebarLayout.ts";
|
||||
import { flattenTree } from "../../apps/desktop/src/composables/useFlatTree.ts";
|
||||
import type { FlatTreeNode } from "../../apps/desktop/src/composables/useFlatTree.ts";
|
||||
import type { QueryTab, TreeNode } from "../../apps/desktop/src/types/database.ts";
|
||||
import type { ConnectionConfig, QueryTab, SidebarLayout, TreeNode } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
function flat(node: TreeNode, depth = 0): FlatTreeNode {
|
||||
return { id: node.id, node, depth, type: node.type };
|
||||
@@ -578,3 +580,59 @@ test("active sidebar selection only scrolls on tab or setting changes", () => {
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("locate under collapsed connection groups resolves the path, expands the groups, then finds the flat node", () => {
|
||||
// Mirrors locateTabInSidebar for a target inside collapsed (nested)
|
||||
// connection groups: the node path ignores expansion, the visible flat tree
|
||||
// does not, so locate must reopen the collapsed groups through the layout op
|
||||
// and rebuild before matching, otherwise the locate silently fails (#7387).
|
||||
const conn = (id: string): ConnectionConfig => ({
|
||||
id,
|
||||
name: id,
|
||||
db_type: "postgres",
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "user",
|
||||
password: "",
|
||||
});
|
||||
const layout: SidebarLayout = {
|
||||
groups: [
|
||||
{ id: "group-outer", name: "Outer", collapsed: true },
|
||||
{ id: "group-inner", name: "Inner", collapsed: true },
|
||||
{ id: "group-open", name: "Open", collapsed: false },
|
||||
],
|
||||
order: [
|
||||
{
|
||||
type: "group",
|
||||
id: "group-outer",
|
||||
children: [{ type: "group", id: "group-inner", children: [{ type: "connection", id: "conn-grouped" }] }],
|
||||
},
|
||||
{ type: "group", id: "group-open", children: [{ type: "connection", id: "conn-open" }] },
|
||||
],
|
||||
};
|
||||
const connections = [conn("conn-grouped"), conn("conn-open")];
|
||||
const target = { type: "connection", connectionId: "conn-grouped" } as const;
|
||||
|
||||
const collapsedTree = buildTreeNodesFromLayout(layout, connections, new Set());
|
||||
const nodePath = findNodePathForTarget(target, collapsedTree);
|
||||
assert.deepEqual(nodePath?.map((node) => node.id), ["group-outer", "group-inner", "conn-grouped"]);
|
||||
assert.equal(findSidebarNodeForTarget(target, flattenTree(collapsedTree)), null);
|
||||
|
||||
const collapsedGroupIds = nodePath!
|
||||
.filter((node) => node.type === "connection-group" && !node.isExpanded)
|
||||
.map((node) => node.id);
|
||||
assert.deepEqual(collapsedGroupIds, ["group-outer", "group-inner"]);
|
||||
|
||||
const expandedLayout = expandGroups(layout, collapsedGroupIds);
|
||||
assert.deepEqual(
|
||||
expandedLayout.groups.map((group) => [group.id, group.collapsed]),
|
||||
[
|
||||
["group-outer", false],
|
||||
["group-inner", false],
|
||||
["group-open", false],
|
||||
],
|
||||
);
|
||||
|
||||
const rebuiltTree = buildTreeNodesFromLayout(expandedLayout, connections, new Set());
|
||||
assert.equal(findSidebarNodeForTarget(target, flattenTree(rebuiltTree))?.id, "conn-grouped");
|
||||
});
|
||||
|
||||
+30
@@ -9,6 +9,7 @@ import {
|
||||
moveConnectionToGroup,
|
||||
reorderEntry,
|
||||
toggleGroupCollapsed,
|
||||
expandGroups,
|
||||
appendConnectionToLayout,
|
||||
removeConnectionFromSidebarLayout,
|
||||
emptyLayout,
|
||||
@@ -381,6 +382,35 @@ test("collapseAllGroups keeps other groups collapsed after one group is reopened
|
||||
);
|
||||
});
|
||||
|
||||
// --- expandGroups ---
|
||||
|
||||
test("expandGroups reopens collapsed groups and is a no-op otherwise", () => {
|
||||
const layout: SidebarLayout = {
|
||||
groups: [
|
||||
{ id: "g1", name: "G1", collapsed: true },
|
||||
{ id: "g2", name: "G2", collapsed: false },
|
||||
],
|
||||
order: [
|
||||
{ type: "group", id: "g1", children: [{ type: "connection", id: "a" }] },
|
||||
{ type: "group", id: "g2", children: [] },
|
||||
],
|
||||
};
|
||||
|
||||
const expanded = expandGroups(layout, ["g1", "g2", "missing"]);
|
||||
assert.deepEqual(
|
||||
expanded.groups.map((group) => [group.id, group.collapsed]),
|
||||
[
|
||||
["g1", false],
|
||||
["g2", false],
|
||||
],
|
||||
);
|
||||
|
||||
// Nothing to reopen: the same layout reference comes back so callers skip
|
||||
// unnecessary rebuilds and persistence.
|
||||
assert.equal(expandGroups(expanded, ["g1"]), expanded);
|
||||
assert.equal(expandGroups(layout, []), layout);
|
||||
});
|
||||
|
||||
// --- moveConnectionToGroup ---
|
||||
|
||||
test("moveConnectionToGroup moves connection into a group", () => {
|
||||
|
||||
Reference in New Issue
Block a user