370 lines
12 KiB
TypeScript
Vendored
370 lines
12 KiB
TypeScript
Vendored
import { test } from "vitest";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
reconcileLayout,
|
|
buildTreeNodesFromLayout,
|
|
createGroup,
|
|
deleteGroup,
|
|
renameGroup,
|
|
moveConnectionToGroup,
|
|
reorderEntry,
|
|
toggleGroupCollapsed,
|
|
appendConnectionToLayout,
|
|
removeConnectionFromSidebarLayout,
|
|
emptyLayout,
|
|
remapSidebarLayoutConnectionIds,
|
|
collapseAllGroups,
|
|
} from "../../apps/desktop/src/lib/sidebar/sidebarLayout.ts";
|
|
import type { ConnectionConfig, SidebarLayout } from "../../apps/desktop/src/types/database.ts";
|
|
|
|
function conn(id: string, name?: string): ConnectionConfig {
|
|
return {
|
|
id,
|
|
name: name || id,
|
|
db_type: "postgres",
|
|
host: "localhost",
|
|
port: 5432,
|
|
username: "user",
|
|
password: "",
|
|
};
|
|
}
|
|
|
|
function groupConnectionIds(entry: SidebarLayout["order"][number]): string[] {
|
|
assert.equal(entry.type, "group");
|
|
return (entry.children ?? []).filter((child) => child.type === "connection").map((child) => child.id);
|
|
}
|
|
|
|
// --- reconcileLayout ---
|
|
|
|
test("reconcileLayout returns all connections ungrouped when layout is null", () => {
|
|
const result = reconcileLayout(["a", "b", "c"], null);
|
|
assert.deepEqual(result.groups, []);
|
|
assert.deepEqual(result.order, [
|
|
{ type: "connection", id: "a" },
|
|
{ type: "connection", id: "b" },
|
|
{ type: "connection", id: "c" },
|
|
]);
|
|
});
|
|
|
|
test("reconcileLayout appends new connections not in layout", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [],
|
|
order: [{ type: "connection", id: "a" }],
|
|
};
|
|
const result = reconcileLayout(["a", "b"], layout);
|
|
assert.equal(result.order.length, 2);
|
|
assert.deepEqual(result.order[1], { type: "connection", id: "b" });
|
|
});
|
|
|
|
test("reconcileLayout removes stale connections from layout", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "Group", collapsed: false }],
|
|
order: [
|
|
{ type: "group", id: "g1", connectionIds: ["a", "removed"] },
|
|
{ type: "connection", id: "b" },
|
|
],
|
|
};
|
|
const result = reconcileLayout(["a", "b"], layout);
|
|
const groupEntry = result.order.find((e) => e.type === "group");
|
|
assert.ok(groupEntry && groupEntry.type === "group");
|
|
assert.deepEqual(groupConnectionIds(groupEntry), ["a"]);
|
|
assert.equal(result.order.length, 2);
|
|
});
|
|
|
|
test("reconcileLayout removes groups with no order entry", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [
|
|
{ id: "g1", name: "Used", collapsed: false },
|
|
{ id: "g2", name: "Orphan", collapsed: false },
|
|
],
|
|
order: [{ type: "group", id: "g1", connectionIds: ["a"] }],
|
|
};
|
|
const result = reconcileLayout(["a"], layout);
|
|
assert.equal(result.groups.length, 1);
|
|
assert.equal(result.groups[0].id, "g1");
|
|
});
|
|
|
|
test("remapSidebarLayoutConnectionIds preserves imported grouping with new connection ids", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "Imported", collapsed: false }],
|
|
order: [
|
|
{ type: "connection", id: "old-root" },
|
|
{ type: "group", id: "g1", connectionIds: ["old-a", "old-b"] },
|
|
],
|
|
};
|
|
|
|
const remapped = remapSidebarLayoutConnectionIds(
|
|
layout,
|
|
new Map([
|
|
["old-root", "new-root"],
|
|
["old-a", "new-a"],
|
|
["old-b", "new-b"],
|
|
]),
|
|
);
|
|
const reconciled = reconcileLayout(["new-root", "new-a", "new-b"], remapped);
|
|
|
|
assert.deepEqual(reconciled.order, [
|
|
{ type: "connection", id: "new-root" },
|
|
{
|
|
type: "group",
|
|
id: "g1",
|
|
children: [
|
|
{ type: "connection", id: "new-a" },
|
|
{ type: "connection", id: "new-b" },
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
|
|
// --- buildTreeNodesFromLayout ---
|
|
|
|
test("buildTreeNodesFromLayout creates group nodes with connection children", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "Production", collapsed: false }],
|
|
order: [
|
|
{ type: "group", id: "g1", connectionIds: ["a", "b"] },
|
|
{ type: "connection", id: "c" },
|
|
],
|
|
};
|
|
const connections = [conn("a", "Server A"), conn("b", "Server B"), conn("c", "Server C")];
|
|
const nodes = buildTreeNodesFromLayout(layout, connections, new Set());
|
|
|
|
assert.equal(nodes.length, 2);
|
|
assert.equal(nodes[0].type, "connection-group");
|
|
assert.equal(nodes[0].label, "Production");
|
|
assert.equal(nodes[0].children?.length, 2);
|
|
assert.equal(nodes[0].isExpanded, true);
|
|
assert.equal(nodes[1].type, "connection");
|
|
assert.equal(nodes[1].id, "c");
|
|
});
|
|
|
|
test("buildTreeNodesFromLayout respects collapsed groups", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: true }],
|
|
order: [{ type: "group", id: "g1", connectionIds: ["a"] }],
|
|
};
|
|
const nodes = buildTreeNodesFromLayout(layout, [conn("a")], new Set());
|
|
assert.equal(nodes[0].isExpanded, false);
|
|
});
|
|
|
|
test("buildTreeNodesFromLayout applies pinning within groups", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: false }],
|
|
order: [{ type: "group", id: "g1", connectionIds: ["a", "b"] }],
|
|
};
|
|
const nodes = buildTreeNodesFromLayout(layout, [conn("a"), conn("b")], new Set(["b"]));
|
|
const children = nodes[0].children!;
|
|
assert.equal(children[0].id, "b");
|
|
assert.equal(children[1].id, "a");
|
|
});
|
|
|
|
// --- createGroup ---
|
|
|
|
test("createGroup adds a new empty group", () => {
|
|
const layout = emptyLayout();
|
|
const result = createGroup(layout, "Dev");
|
|
assert.equal(result.layout.groups.length, 1);
|
|
assert.equal(result.layout.groups[0].name, "Dev");
|
|
assert.equal(result.layout.order.length, 1);
|
|
assert.ok(result.layout.order[0].type === "group");
|
|
});
|
|
|
|
test("createGroup expands parent group when adding a subgroup", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "Parent", collapsed: true }],
|
|
order: [{ type: "group", id: "g1", children: [] }],
|
|
};
|
|
|
|
const result = createGroup(layout, "Child", "g1");
|
|
const parentGroup = result.layout.groups.find((group) => group.id === "g1");
|
|
const parentEntry = result.layout.order[0];
|
|
|
|
assert.equal(parentGroup?.collapsed, false);
|
|
assert.equal(parentEntry.type, "group");
|
|
assert.deepEqual(parentEntry.type === "group" ? parentEntry.children : undefined, [{ type: "group", id: result.groupId, children: [] }]);
|
|
});
|
|
|
|
// --- renameGroup ---
|
|
|
|
test("renameGroup updates group name", () => {
|
|
const { layout, groupId } = createGroup(emptyLayout(), "Old");
|
|
const result = renameGroup(layout, groupId, "New");
|
|
assert.equal(result.groups[0].name, "New");
|
|
});
|
|
|
|
// --- deleteGroup ---
|
|
|
|
test("deleteGroup moves connections to ungrouped", () => {
|
|
let layout = emptyLayout();
|
|
layout = appendConnectionToLayout(layout, "a");
|
|
const { layout: withGroup, groupId } = createGroup(layout, "G");
|
|
const moved = moveConnectionToGroup(withGroup, "a", groupId);
|
|
const result = deleteGroup(moved, groupId);
|
|
|
|
assert.equal(result.groups.length, 0);
|
|
assert.deepEqual(result.order, [{ type: "connection", id: "a" }]);
|
|
});
|
|
|
|
// --- toggleGroupCollapsed ---
|
|
|
|
test("toggleGroupCollapsed flips collapsed state", () => {
|
|
const { layout, groupId } = createGroup(emptyLayout(), "G");
|
|
assert.equal(layout.groups[0].collapsed, false);
|
|
const toggled = toggleGroupCollapsed(layout, groupId);
|
|
assert.equal(toggled.groups[0].collapsed, true);
|
|
});
|
|
|
|
test("collapseAllGroups keeps other groups collapsed after one group is reopened", async () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [
|
|
{ id: "g1", name: "G1", collapsed: false },
|
|
{ id: "g2", name: "G2", collapsed: false },
|
|
],
|
|
order: [
|
|
{ type: "group", id: "g1", children: [{ type: "connection", id: "a" }] },
|
|
{ type: "group", id: "g2", children: [{ type: "connection", id: "b" }] },
|
|
],
|
|
};
|
|
|
|
const collapsed = collapseAllGroups(layout);
|
|
assert.deepEqual(
|
|
collapsed.groups.map((group) => [group.id, group.collapsed]),
|
|
[
|
|
["g1", true],
|
|
["g2", true],
|
|
],
|
|
);
|
|
|
|
const reopenedFirst = toggleGroupCollapsed(collapsed, "g1");
|
|
assert.deepEqual(
|
|
reopenedFirst.groups.map((group) => [group.id, group.collapsed]),
|
|
[
|
|
["g1", false],
|
|
["g2", true],
|
|
],
|
|
);
|
|
});
|
|
|
|
// --- moveConnectionToGroup ---
|
|
|
|
test("moveConnectionToGroup moves connection into a group", () => {
|
|
let layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: false }],
|
|
order: [
|
|
{ type: "group", id: "g1", connectionIds: [] },
|
|
{ type: "connection", id: "a" },
|
|
],
|
|
};
|
|
const result = moveConnectionToGroup(layout, "a", "g1");
|
|
const groupEntry = result.order.find((e) => e.type === "group" && e.id === "g1");
|
|
assert.ok(groupEntry && groupEntry.type === "group");
|
|
assert.deepEqual(groupConnectionIds(groupEntry), ["a"]);
|
|
assert.equal(result.order.length, 1);
|
|
});
|
|
|
|
test("moveConnectionToGroup moves connection out of a group", () => {
|
|
let layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: false }],
|
|
order: [{ type: "group", id: "g1", connectionIds: ["a"] }],
|
|
};
|
|
const result = moveConnectionToGroup(layout, "a", null);
|
|
assert.equal(result.order.length, 2);
|
|
assert.deepEqual(result.order[1], { type: "connection", id: "a" });
|
|
});
|
|
|
|
// --- reorderEntry ---
|
|
|
|
test("reorderEntry moves connection before another", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [],
|
|
order: [
|
|
{ type: "connection", id: "a" },
|
|
{ type: "connection", id: "b" },
|
|
{ type: "connection", id: "c" },
|
|
],
|
|
};
|
|
const result = reorderEntry(layout, "c", "a", "before");
|
|
assert.deepEqual(
|
|
result.order.map((e) => e.id),
|
|
["c", "a", "b"],
|
|
);
|
|
});
|
|
|
|
test("reorderEntry moves connection after another", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [],
|
|
order: [
|
|
{ type: "connection", id: "a" },
|
|
{ type: "connection", id: "b" },
|
|
{ type: "connection", id: "c" },
|
|
],
|
|
};
|
|
const result = reorderEntry(layout, "a", "b", "after");
|
|
assert.deepEqual(
|
|
result.order.map((e) => e.id),
|
|
["b", "a", "c"],
|
|
);
|
|
});
|
|
|
|
test("reorderEntry moves connection inside a group", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: false }],
|
|
order: [
|
|
{ type: "group", id: "g1", connectionIds: [] },
|
|
{ type: "connection", id: "a" },
|
|
],
|
|
};
|
|
const result = reorderEntry(layout, "a", "g1", "inside");
|
|
assert.equal(result.order.length, 1);
|
|
const groupEntry = result.order[0];
|
|
assert.ok(groupEntry.type === "group");
|
|
assert.deepEqual(groupConnectionIds(groupEntry), ["a"]);
|
|
});
|
|
|
|
test("reorderEntry is a no-op when dragging to same position", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [],
|
|
order: [{ type: "connection", id: "a" }],
|
|
};
|
|
const result = reorderEntry(layout, "a", "a", "before");
|
|
assert.deepEqual(result, layout);
|
|
});
|
|
|
|
// --- appendConnectionToLayout / removeConnectionFromSidebarLayout ---
|
|
|
|
test("appendConnectionToLayout adds to the end", () => {
|
|
const layout = appendConnectionToLayout(emptyLayout(), "x");
|
|
assert.equal(layout.order.length, 1);
|
|
assert.deepEqual(layout.order[0], { type: "connection", id: "x" });
|
|
});
|
|
|
|
test("appendConnectionToLayout adds to target group and expands it", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: true }],
|
|
order: [{ type: "group", id: "g1", connectionIds: ["a"] }],
|
|
};
|
|
const result = appendConnectionToLayout(layout, "b", "g1");
|
|
const groupEntry = result.order[0];
|
|
|
|
assert.equal(result.groups[0].collapsed, false);
|
|
assert.ok(groupEntry.type === "group");
|
|
assert.deepEqual(groupConnectionIds(groupEntry), ["a", "b"]);
|
|
});
|
|
|
|
test("removeConnectionFromSidebarLayout removes from ungrouped", () => {
|
|
let layout = appendConnectionToLayout(emptyLayout(), "x");
|
|
layout = removeConnectionFromSidebarLayout(layout, "x");
|
|
assert.equal(layout.order.length, 0);
|
|
});
|
|
|
|
test("removeConnectionFromSidebarLayout removes from inside a group", () => {
|
|
const layout: SidebarLayout = {
|
|
groups: [{ id: "g1", name: "G", collapsed: false }],
|
|
order: [{ type: "group", id: "g1", connectionIds: ["a", "b"] }],
|
|
};
|
|
const result = removeConnectionFromSidebarLayout(layout, "a");
|
|
const groupEntry = result.order[0];
|
|
assert.ok(groupEntry.type === "group");
|
|
assert.deepEqual(groupConnectionIds(groupEntry), ["b"]);
|
|
});
|