fix(sidebar): pin vgroup rows before flat rows on sort

This commit is contained in:
zipg authored and GitHub committed 2026-09-20 12:52:50 +08:00
1 parent 33afb0b432
commit 758d21167a
4 files changed
+115 -8

No files matched your search

@@ -0,0 +1,46 @@
import { describe, expect, it } from "vitest";
import { sortSidebarTreeChildrenByNameKeepingTableVGroups } from "@/lib/sidebar/sidebarNodeOrdering";
import { applyTableVGroupsToChildren, createTableVGroup, emptyTableVGroupLayout, moveTableToVGroup } from "@/lib/table/tableVGroup";
import type { TreeNode } from "@/types/database";
const SCOPE = { connectionId: "conn-1", database: "db1" };
function tableNode(name: string): TreeNode {
return { id: `table:${name}`, label: name, type: "table", connectionId: "conn-1", database: "db1" };
}
/** Mirrors connectionStore.mergeLocatedTreeChildren: merge a page, then re-sort. */
function mergePageLikeStore(parent: TreeNode, children: TreeNode[], page: TreeNode[]): TreeNode[] {
return sortSidebarTreeChildrenByNameKeepingTableVGroups(parent, [...children, ...page], "mysql");
}
describe("sidebar ordering keeps projected table groups pinned", () => {
it("keeps a virtual table group at the top after a paged merge re-sorts children by name", () => {
const created = createTableVGroup(emptyTableVGroupLayout(), "zzz-分组");
const layout = moveTableToVGroup(created.layout, "t_order", created.groupId);
const parent: TreeNode = { id: "group-tables:db1", label: "tables", type: "group-tables", connectionId: "conn-1", database: "db1" };
// What the sidebar shows after the layout projection runs.
const projected = applyTableVGroupsToChildren([tableNode("t_user"), tableNode("t_order")], layout, SCOPE);
expect(projected[0]!.type).toBe("table-vgroup");
// A sidebar page merge must not move the projected group out of the top
// slot: the layout, not the alphabet, decides where a group sits.
const merged = mergePageLikeStore(parent, projected, [tableNode("t_zzz")]);
expect(merged[0]!.type).toBe("table-vgroup");
expect(merged.slice(1).map((node) => node.label)).toEqual(["t_user", "t_zzz"]);
expect(merged[0]!.children?.map((node) => node.label)).toEqual(["t_order"]);
});
it("keeps projected groups in layout order when their labels sort differently", () => {
const first = createTableVGroup(emptyTableVGroupLayout(), "b-组");
let layout = moveTableToVGroup(first.layout, "t_order", first.groupId);
const second = createTableVGroup(layout, "a-组");
layout = moveTableToVGroup(second.layout, "t_pay", second.groupId);
const parent: TreeNode = { id: "group-tables:db1", label: "tables", type: "group-tables", connectionId: "conn-1", database: "db1" };
const projected = applyTableVGroupsToChildren([tableNode("t_user"), tableNode("t_order"), tableNode("t_pay")], layout, SCOPE);
const merged = mergePageLikeStore(parent, projected, [tableNode("t_zzz")]);
expect(merged.filter((node) => node.type === "table-vgroup").map((node) => node.label)).toEqual(["b-组", "a-组"]);
});
});
@@ -17,7 +17,38 @@ function sortRecursive(node: TreeNode, databaseType?: DatabaseType): TreeNode {
};
}
/**
* Virtual table groups are a display-level projection (see
* `applyTableVGroupsToChildren`) whose order comes from the stored layout, not
* from the alphabet. Every ordering pass therefore has to leave them ahead of
* the flat rows: a name-sorted metadata merge would otherwise drop a group back
* to its alphabetical slot, and the row would visibly jump from the top of the
* list to the bottom until the next projection ran (issues #9644 / #9653).
*/
function hoistTableVGroupChildren(nodes: TreeNode[]): TreeNode[] {
if (!nodes.some((node) => node.type === "table-vgroup")) return nodes;
return [...nodes.filter((node) => node.type === "table-vgroup"), ...nodes.filter((node) => node.type !== "table-vgroup")];
}
export function sortSidebarTreeChildrenForParent(parent: Pick<TreeNode, "type">, children: readonly TreeNode[], databaseType?: DatabaseType): TreeNode[] {
return hoistTableVGroupChildren(orderSidebarTreeChildrenForParent(parent, children, databaseType));
}
/**
* Orders a container whose rows are merged from paged metadata: only the flat
* rows are sorted by label, while projected virtual table groups keep the
* position (and relative order) the layout projection gave them. Sorting the
* groups by name would move them out of the top slot they were projected into
* (issues #9644 / #9653).
*/
export function sortSidebarTreeChildrenByNameKeepingTableVGroups(parent: Pick<TreeNode, "type">, children: readonly TreeNode[], databaseType?: DatabaseType): TreeNode[] {
const groups = children.filter((node) => node.type === "table-vgroup");
const flat = children.filter((node) => node.type !== "table-vgroup");
const orderedFlat = [...flat].sort((left, right) => sidebarTreeCollator.compare(left.label, right.label));
return sortSidebarTreeChildrenForParent(parent, [...groups, ...orderedFlat], databaseType);
}
function orderSidebarTreeChildrenForParent(parent: Pick<TreeNode, "type">, children: readonly TreeNode[], databaseType?: DatabaseType): TreeNode[] {
const normalized = children.map((child) => sortRecursive(child, databaseType));
if (parent.type === "mongo-db") {