fix(sidebar): avoid synchronous selection pruning

This commit is contained in:
t8y2 committed 2026-08-18 15:47:44 +08:00
1 parent 94949eb1c7
commit 0b49c5a761
4 files changed
+59 -12

No files matched your search

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { filterSidebarModifierSelectionIds, selectedSidebarBatchTargets, supportsSidebarModifierSelection } from "@/lib/sidebar/sidebarTreeSelection";
import { describe, expect, it, vi } from "vitest";
import { filterSidebarModifierSelectionIds, pruneTreeSelectionToVisibleNodeIds, selectedSidebarBatchTargets, supportsSidebarModifierSelection } from "@/lib/sidebar/sidebarTreeSelection";
import type { TreeNode } from "@/types/database";
function node(id: string, type: TreeNode["type"]): TreeNode {
@@ -32,6 +32,28 @@ describe("sidebar modifier selection", () => {
});
});
describe("visible sidebar selection pruning", () => {
it("checks only selected identifiers against the existing visible-node index", () => {
const has = vi.fn((id: string) => id === "table-2");
expect(
pruneTreeSelectionToVisibleNodeIds(
{ has },
{
nodeIds: ["table-1", "table-2", "table-3"],
activeNodeId: "table-3",
anchorNodeId: "table-1",
},
),
).toEqual({
nodeIds: ["table-2"],
activeNodeId: "table-2",
anchorNodeId: "table-2",
});
expect(has).toHaveBeenCalledTimes(5);
});
});
describe("sidebar batch action targets", () => {
const table = (id: string, connectionId = "connection-1", database = "database-1"): TreeNode => ({
id,
@@ -6,6 +6,10 @@ export type VisibleTreeSelection = {
anchorNodeId: string | null;
};
export type VisibleTreeNodeIdLookup = {
has(nodeId: string): boolean;
};
export function supportsSidebarModifierSelection(node: Pick<TreeNode, "type">): boolean {
return node.type !== "database" && node.type !== "schema" && !node.type.startsWith("group-");
}
@@ -21,14 +25,17 @@ export function selectedTreeNodesInVisibleOrder(visibleNodes: TreeNode[], select
return visibleNodes.filter((node) => ids.has(node.id));
}
export function pruneTreeSelectionToVisibleNodes(visibleNodes: readonly TreeNode[], selection: VisibleTreeSelection): VisibleTreeSelection {
const visibleIds = new Set(visibleNodes.map((node) => node.id));
const nodeIds = selection.nodeIds.filter((id) => visibleIds.has(id));
const activeNodeId = selection.activeNodeId && visibleIds.has(selection.activeNodeId) ? selection.activeNodeId : (nodeIds[0] ?? null);
const anchorNodeId = selection.anchorNodeId && visibleIds.has(selection.anchorNodeId) ? selection.anchorNodeId : activeNodeId;
export function pruneTreeSelectionToVisibleNodeIds(visibleNodeIds: VisibleTreeNodeIdLookup, selection: VisibleTreeSelection): VisibleTreeSelection {
const nodeIds = selection.nodeIds.filter((id) => visibleNodeIds.has(id));
const activeNodeId = selection.activeNodeId && visibleNodeIds.has(selection.activeNodeId) ? selection.activeNodeId : (nodeIds[0] ?? null);
const anchorNodeId = selection.anchorNodeId && visibleNodeIds.has(selection.anchorNodeId) ? selection.anchorNodeId : activeNodeId;
return { nodeIds, activeNodeId, anchorNodeId };
}
export function pruneTreeSelectionToVisibleNodes(visibleNodes: readonly TreeNode[], selection: VisibleTreeSelection): VisibleTreeSelection {
return pruneTreeSelectionToVisibleNodeIds(new Set(visibleNodes.map((node) => node.id)), selection);
}
export function selectedSidebarBatchTargets(activeNode: TreeNode, selectedNodes: readonly TreeNode[], canUseTarget: (node: TreeNode) => boolean): TreeNode[] {
if (selectedNodes.length <= 1 || !selectedNodes.some((node) => node.id === activeNode.id)) return [];
const first = selectedNodes[0];