Files
2026-07-13 13:09:14 +08:00

347 lines
12 KiB
TypeScript
Vendored

import { test } from "vitest";
import assert from "node:assert/strict";
import { buildGroupedObjectTreeNodes, buildObjectGroupPlaceholderNodes, buildSimpleObjectTreeNodes, buildTableTreeNodes, filterSimpleSidebarSupplementalObjects, mergeTableInfosIntoObjects, mergeTableTreePageChildren } from "../../apps/desktop/src/lib/table/tableTree.ts";
import type { ObjectInfo, TableInfo, TreeNode } from "../../apps/desktop/src/types/database.ts";
function table(name: string, parent?: string): TableInfo {
return {
name,
table_type: "BASE TABLE",
comment: null,
parent_schema: parent ? "public" : null,
parent_name: parent ?? null,
};
}
function object(name: string, parent?: string): ObjectInfo {
return {
name,
object_type: "TABLE",
schema: "public",
comment: null,
created_at: null,
updated_at: null,
parent_schema: parent ? "public" : null,
parent_name: parent ?? null,
};
}
function partitionGroup(node: TreeNode): TreeNode | undefined {
return node.children?.find((child) => child.type === "group-partitions");
}
test("buildTableTreeNodes nests multi-level table partitions", () => {
const nodes = buildTableTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
tables: [table("events"), table("events_2026", "events"), table("events_2026_05", "events_2026"), table("users")],
});
assert.deepEqual(
nodes.map((node) => node.label),
["events", "users"],
);
assert.equal(nodes[0].id, "conn:app:public:events");
const events = nodes[0];
const firstLevel = partitionGroup(events);
assert.equal(firstLevel?.label, "tree.partitions");
assert.deepEqual(
firstLevel?.children?.map((node) => node.label),
["events_2026"],
);
const secondLevel = partitionGroup(firstLevel!.children![0]);
assert.deepEqual(
secondLevel?.children?.map((node) => node.label),
["events_2026_05"],
);
});
test("buildTableTreeNodes keeps partitions visible when their parent is not loaded", () => {
const nodes = buildTableTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
tables: [table("events_2026", "events")],
});
assert.deepEqual(
nodes.map((node) => node.label),
["events_2026"],
);
});
test("mergeTableTreePageChildren attaches later page partitions to loaded parents", () => {
const firstPage = buildTableTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
tables: [table("events"), table("events_region_0", "events")],
});
const secondPage = buildTableTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
tables: [table("events_region_0_2026_01", "events_region_0")],
});
const merged = mergeTableTreePageChildren(firstPage, secondPage, "conn", "app");
assert.deepEqual(
merged.map((node) => node.label),
["events"],
);
const regionPartition = partitionGroup(merged[0])?.children?.[0];
assert.equal(regionPartition?.label, "events_region_0");
assert.deepEqual(
partitionGroup(regionPartition!)?.children?.map((node) => node.label),
["events_region_0_2026_01"],
);
});
test("buildTableTreeNodes keeps sidebar tables in natural name order", () => {
const nodes = buildTableTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
tables: [table("chat_staff"), table("chat_staff_his"), table("staff"), table("staff_his")],
});
assert.deepEqual(
nodes.map((node) => node.label),
["chat_staff", "chat_staff_his", "staff", "staff_his"],
);
});
test("buildTableTreeNodes does not relocate prefixed business tables by suffix", () => {
const nodes = buildTableTreeNodes({
nodeId: "conn:app",
connectionId: "conn",
database: "app",
tables: [table("CurrentStock"), table("YonSuite_CurrentStock"), table("YonSuite_LocationStock")],
});
assert.deepEqual(
nodes.map((node) => node.label),
["CurrentStock", "YonSuite_CurrentStock", "YonSuite_LocationStock"],
);
});
test("buildGroupedObjectTreeNodes nests partitions inside the tables group", () => {
const groups = buildGroupedObjectTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
objects: [object("events"), object("events_2026", "events"), object("events_2026_05", "events_2026")],
});
const tableGroup = groups.find((node) => node.type === "group-tables");
assert.equal(tableGroup?.objectCount, 3);
assert.deepEqual(
tableGroup?.children?.map((node) => node.label),
["events"],
);
assert.equal(tableGroup?.children?.[0]?.id, "conn:app:public:__tables:public:events");
assert.deepEqual(
partitionGroup(tableGroup!.children![0])?.children?.map((node) => node.label),
["events_2026"],
);
});
test("buildGroupedObjectTreeNodes applies natural name sorting inside object groups", () => {
const groups = buildGroupedObjectTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
objects: [object("chat_staff"), object("chat_staff_his"), object("staff"), object("staff_his")],
});
const tableGroup = groups.find((node) => node.type === "group-tables");
assert.deepEqual(
tableGroup?.children?.map((node) => node.label),
["chat_staff", "chat_staff_his", "staff", "staff_his"],
);
});
test("buildGroupedObjectTreeNodes groups Oracle packages and package bodies", () => {
const groups = buildGroupedObjectTreeNodes({
nodeId: "conn:app:HR",
connectionId: "conn",
database: "app",
schema: "HR",
objects: [
{ name: "PAYROLL", object_type: "PACKAGE", schema: "HR" },
{ name: "PAYROLL", object_type: "PACKAGE_BODY", schema: "HR" },
],
});
const packageGroup = groups.find((node) => node.type === "group-packages");
assert.equal(packageGroup?.label, "tree.packages");
assert.deepEqual(
packageGroup?.children?.map((node) => ({ label: node.label, type: node.type, id: node.id })),
[
{ label: "PAYROLL", type: "package", id: "conn:app:HR:__packages:HR:PAYROLL:PACKAGE" },
{ label: "PAYROLL", type: "package-body", id: "conn:app:HR:__packages:HR:PAYROLL:PACKAGE_BODY" },
],
);
});
test("buildGroupedObjectTreeNodes groups materialized views separately from views", () => {
const groups = buildGroupedObjectTreeNodes({
nodeId: "conn:app:APP",
connectionId: "conn",
database: "app",
schema: "APP",
objects: [
{ name: "ACTIVE_USERS", object_type: "VIEW", schema: "APP" },
{ name: "USER_SUMMARY_MV", object_type: "MATERIALIZED_VIEW", schema: "APP" },
],
});
const viewGroup = groups.find((node) => node.type === "group-views");
const materializedViewGroup = groups.find((node) => node.type === "group-materialized-views");
assert.deepEqual(
viewGroup?.children?.map((node) => ({ label: node.label, type: node.type })),
[{ label: "ACTIVE_USERS", type: "view" }],
);
assert.deepEqual(
materializedViewGroup?.children?.map((node) => ({ label: node.label, type: node.type })),
[{ label: "USER_SUMMARY_MV", type: "materialized_view" }],
);
});
test("buildObjectGroupPlaceholderNodes creates capability-driven lazy sidebar groups", () => {
const groups = buildObjectGroupPlaceholderNodes({
nodeId: "conn:app:HR",
connectionId: "conn",
database: "app",
schema: "HR",
objectTypes: ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE"],
});
assert.deepEqual(
groups.map((node) => ({ label: node.label, type: node.type, count: node.objectCount, children: node.children })),
[
{ label: "tree.tables", type: "group-tables", count: undefined, children: [] },
{ label: "tree.views", type: "group-views", count: undefined, children: [] },
{ label: "tree.procedures", type: "group-procedures", count: undefined, children: [] },
{ label: "tree.functions", type: "group-functions", count: undefined, children: [] },
{ label: "tree.sequences", type: "group-sequences", count: undefined, children: [] },
],
);
});
test("buildSimpleObjectTreeNodes keeps routines, sequences, and packages visible in flat sidebar mode", () => {
const nodes = buildSimpleObjectTreeNodes({
nodeId: "conn:app:HR",
connectionId: "conn",
database: "app",
schema: "HR",
objects: [
{ name: "ORDERS", object_type: "TABLE", schema: "HR" },
{ name: "ACTIVE_ORDERS", object_type: "VIEW", schema: "HR" },
{ name: "REFRESH_STATS", object_type: "PROCEDURE", schema: "HR" },
{ name: "TOTAL_DUE", object_type: "FUNCTION", schema: "HR" },
{ name: "ORDER_ID_SEQ", object_type: "SEQUENCE", schema: "HR" },
{ name: "PAYROLL", object_type: "PACKAGE", schema: "HR" },
{ name: "PAYROLL", object_type: "PACKAGE_BODY", schema: "HR" },
],
});
assert.deepEqual(
nodes.map((node) => ({ label: node.label, type: node.type })),
[
{ label: "ORDERS", type: "table" },
{ label: "ACTIVE_ORDERS", type: "view" },
{ label: "ORDER_ID_SEQ", type: "sequence" },
{ label: "PAYROLL", type: "package" },
{ label: "PAYROLL", type: "package-body" },
{ label: "REFRESH_STATS", type: "procedure" },
{ label: "TOTAL_DUE", type: "function" },
],
);
});
test("mergeTableInfosIntoObjects restores views missing from object metadata", () => {
const merged = mergeTableInfosIntoObjects(
[object("orders")],
[
table("orders"),
{
name: "active_orders",
table_type: "VIEW",
comment: "current orders",
parent_schema: null,
parent_name: null,
},
],
"public",
);
assert.deepEqual(
merged.map((item) => ({ name: item.name, type: item.object_type, schema: item.schema, comment: item.comment })),
[
{ name: "orders", type: "TABLE", schema: "public", comment: null },
{ name: "active_orders", type: "VIEW", schema: "public", comment: "current orders" },
],
);
});
test("filterSimpleSidebarSupplementalObjects leaves paged tables and views to listTables", () => {
const supplemental = filterSimpleSidebarSupplementalObjects([
{ name: "orders", object_type: "TABLE", schema: "public" },
{ name: "active_orders", object_type: "VIEW", schema: "public" },
{ name: "order_summary", object_type: "MATERIALIZED VIEW", schema: "public" },
{ name: "refresh_stats", object_type: "PROCEDURE", schema: "public" },
{ name: "total_due", object_type: "FUNCTION", schema: "public" },
]);
const nodes = buildSimpleObjectTreeNodes({
nodeId: "conn:app:public",
connectionId: "conn",
database: "app",
schema: "public",
objects: mergeTableInfosIntoObjects(supplemental, [table("orders")], "public"),
});
assert.deepEqual(
nodes.map((node) => ({ label: node.label, type: node.type })),
[
{ label: "orders", type: "table" },
{ label: "refresh_stats", type: "procedure" },
{ label: "total_due", type: "function" },
],
);
});
test("mergeTableInfosIntoObjects dedupes MySQL tables when object metadata carries database as schema", () => {
const merged = mergeTableInfosIntoObjects(
[
{
name: "orders",
object_type: "TABLE",
schema: "app",
comment: null,
created_at: null,
updated_at: null,
parent_schema: null,
parent_name: null,
},
],
[table("orders")],
);
assert.deepEqual(
merged.map((item) => ({ name: item.name, type: item.object_type, schema: item.schema })),
[{ name: "orders", type: "TABLE", schema: "app" }],
);
});