165 lines
7.6 KiB
TypeScript
Vendored
165 lines
7.6 KiB
TypeScript
Vendored
import { describe, expect, it } from "vitest";
|
|
import { orderItemsByPinnedTreeNodeOrder, removePinnedTreeNodesFromOrder, treeNodePinIdentity, treeNodePinKey } from "@/lib/app/pinnedItems";
|
|
import { buildMongoObjectBrowserRows, buildObjectBrowserRows, canonicalizeObjectBrowserPinnedTreeNodeIdentity, objectBrowserRowLegacyPinnedTreeNodeIds, objectBrowserRowMatchesPinnedTreeNode, sortObjectBrowserRows, type ObjectBrowserRow } from "@/lib/table/objectBrowserRows";
|
|
import type { TreeNode } from "@/types/database";
|
|
|
|
describe("buildObjectBrowserRows", () => {
|
|
it("preserves a resolved SQLite attached-database alias on every row", () => {
|
|
const rows = buildObjectBrowserRows({
|
|
objects: [{ name: "events", object_type: "TABLE" }],
|
|
database: "analytics",
|
|
fallbackSchema: "analytics",
|
|
rowSchema: "analytics",
|
|
});
|
|
|
|
expect(rows).toHaveLength(1);
|
|
expect(rows[0]?.schema).toBe("analytics");
|
|
});
|
|
|
|
it("keeps non-schema database rows unqualified when no row namespace is resolved", () => {
|
|
const rows = buildObjectBrowserRows({
|
|
objects: [{ name: "events", object_type: "TABLE" }],
|
|
database: "app",
|
|
fallbackSchema: "app",
|
|
});
|
|
|
|
expect(rows[0]?.schema).toBeUndefined();
|
|
});
|
|
|
|
it("preserves nullable view validity in browser rows", () => {
|
|
const rows = buildObjectBrowserRows({
|
|
objects: [
|
|
{ name: "valid_view", object_type: "VIEW", valid: true },
|
|
{ name: "invalid_view", object_type: "VIEW", valid: false },
|
|
{ name: "unknown_view", object_type: "VIEW", valid: null },
|
|
],
|
|
database: "app",
|
|
fallbackSchema: "app",
|
|
});
|
|
|
|
expect(rows.map((row) => row.valid)).toEqual([true, false, null]);
|
|
});
|
|
});
|
|
|
|
describe("Object Browser pinned ordering", () => {
|
|
const context = { connectionId: "conn", database: "app", schema: "public" };
|
|
|
|
function tableNode(name: string, schema = "public"): TreeNode {
|
|
return {
|
|
id: `conn:app:${schema}:tables:${name}`,
|
|
label: name,
|
|
type: "table",
|
|
connectionId: "conn",
|
|
database: "app",
|
|
schema,
|
|
};
|
|
}
|
|
|
|
function orderRows(rows: ObjectBrowserRow[], pinnedNodes: TreeNode[], extraPinnedKeys: string[] = []): ObjectBrowserRow[] {
|
|
const pinnedOrder = [...extraPinnedKeys, ...pinnedNodes.map(treeNodePinKey)];
|
|
return orderItemsByPinnedTreeNodeOrder(rows, pinnedOrder, (row, identity) => objectBrowserRowMatchesPinnedTreeNode(row, identity, context));
|
|
}
|
|
|
|
it("uses the sidebar's persisted custom pin order before the selected name sort", () => {
|
|
const rows = buildObjectBrowserRows({
|
|
objects: ["a1", "a2", "a3", "a4", "a5", "aaa"].map((name) => ({ name, object_type: "TABLE", schema: "public" })),
|
|
database: "app",
|
|
fallbackSchema: "public",
|
|
rowSchema: "public",
|
|
});
|
|
const sorted = sortObjectBrowserRows(rows, "name", "asc");
|
|
const pinnedNodes = ["aaa", "a5", "a4", "a3", "a2", "a1"].map((name) => tableNode(name));
|
|
|
|
const ordered = orderRows(sorted, pinnedNodes, [treeNodePinKey({ ...tableNode("other"), connectionId: "other-connection" })]);
|
|
|
|
expect(ordered.map((row) => row.name)).toEqual(["aaa", "a5", "a4", "a3", "a2", "a1"]);
|
|
});
|
|
|
|
it("keeps unpinned rows in the Object Browser's selected column order", () => {
|
|
const rows: ObjectBrowserRow[] = [
|
|
{ id: "a1", name: "a1", displayName: "a1", schema: "public", type: "TABLE", totalBytes: 30 },
|
|
{ id: "a2", name: "a2", displayName: "a2", schema: "public", type: "TABLE", totalBytes: 40 },
|
|
{ id: "a3", name: "a3", displayName: "a3", schema: "public", type: "TABLE", totalBytes: 10 },
|
|
{ id: "a4", name: "a4", displayName: "a4", schema: "public", type: "TABLE", totalBytes: 20 },
|
|
];
|
|
const sorted = sortObjectBrowserRows(rows, "totalBytes", "desc");
|
|
|
|
const ordered = orderRows(sorted, [tableNode("a3"), tableNode("a1")]);
|
|
|
|
expect(ordered.map((row) => row.name)).toEqual(["a3", "a1", "a2", "a4"]);
|
|
});
|
|
|
|
it("matches a sidebar database child when Object Browser reports the database name as its schema", () => {
|
|
const row: ObjectBrowserRow = { id: "a1", name: "a1", displayName: "a1", schema: "app", type: "TABLE" };
|
|
const sidebarNode = tableNode("a1", "");
|
|
|
|
expect(objectBrowserRowMatchesPinnedTreeNode(row, treeNodePinIdentity(sidebarNode), { connectionId: "conn", database: "app", schema: "app" })).toBe(true);
|
|
});
|
|
|
|
it("clears the database-as-schema alias when an Object Browser object is deleted", () => {
|
|
const sidebarNode = tableNode("events", "");
|
|
const objectBrowserNode: TreeNode = { ...sidebarNode, id: "object-browser:app:events", schema: "app" };
|
|
const canonicalize = canonicalizeObjectBrowserPinnedTreeNodeIdentity({ connectionId: "conn", database: "app" });
|
|
|
|
const remainingOrder = removePinnedTreeNodesFromOrder([treeNodePinKey(sidebarNode)], [objectBrowserNode], canonicalize);
|
|
|
|
expect(remainingOrder).toEqual([]);
|
|
});
|
|
|
|
it("reconstructs legacy sidebar IDs for an unloaded Object Browser row", () => {
|
|
const row: ObjectBrowserRow = { id: "object-browser:events:0", name: "events", displayName: "events", schema: "public", type: "TABLE" };
|
|
const ids = objectBrowserRowLegacyPinnedTreeNodeIds(row, { connectionId: "conn", database: "app", schema: "public", sidebarParentId: "conn:app:public" });
|
|
|
|
expect(ids).toEqual(expect.arrayContaining(["conn:app:public:events", "conn:app:public:__tables:public:events"]));
|
|
});
|
|
|
|
it("does not confuse same-name objects across schemas or routine overloads", () => {
|
|
const tableRow: ObjectBrowserRow = { id: "orders", name: "orders", displayName: "orders", schema: "public", type: "TABLE" };
|
|
expect(objectBrowserRowMatchesPinnedTreeNode(tableRow, treeNodePinIdentity(tableNode("orders", "archive")), context)).toBe(false);
|
|
expect(objectBrowserRowMatchesPinnedTreeNode(tableRow, treeNodePinIdentity(tableNode("orders", "public")), context)).toBe(true);
|
|
|
|
const routineRow: ObjectBrowserRow = { id: "run-int", name: "run", displayName: "run(integer)", schema: "public", type: "FUNCTION", signature: "integer" };
|
|
const routineNode: TreeNode = {
|
|
id: "conn:app:public:functions:run:text",
|
|
label: "run(text)",
|
|
objectName: "run",
|
|
signature: "text",
|
|
type: "function",
|
|
connectionId: "conn",
|
|
database: "app",
|
|
schema: "public",
|
|
};
|
|
expect(objectBrowserRowMatchesPinnedTreeNode(routineRow, treeNodePinIdentity(routineNode), context)).toBe(false);
|
|
expect(objectBrowserRowMatchesPinnedTreeNode(routineRow, treeNodePinIdentity({ ...routineNode, id: "conn:app:public:functions:run:integer", label: "run(integer)", signature: "integer" }), context)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("buildMongoObjectBrowserRows", () => {
|
|
it("maps collection kinds and keeps collection names searchable", () => {
|
|
const rows = buildMongoObjectBrowserRows({
|
|
collections: [
|
|
{ name: "users", kind: "collection" },
|
|
{ name: "active_users", kind: "view" },
|
|
{ name: "metrics", kind: "timeseries" },
|
|
],
|
|
database: "app",
|
|
});
|
|
|
|
expect(rows.map((row) => ({ name: row.name, type: row.type, collectionKind: row.collectionKind }))).toEqual([
|
|
{ name: "users", type: "TABLE", collectionKind: "collection" },
|
|
{ name: "active_users", type: "VIEW", collectionKind: "view" },
|
|
{ name: "metrics", type: "TABLE", collectionKind: "timeseries" },
|
|
]);
|
|
});
|
|
|
|
it("preserves distinct MongoDB collection identifiers verbatim", () => {
|
|
const rows = buildMongoObjectBrowserRows({
|
|
collections: [{ name: " users " }, { name: "users" }, { name: " " }, { name: "" }],
|
|
database: "app",
|
|
});
|
|
|
|
expect(rows.map((row) => row.name)).toEqual([" users ", "users", " "]);
|
|
expect(new Set(rows.map((row) => row.id))).toHaveLength(3);
|
|
});
|
|
});
|