Add tree utility and tests
CI/CD Pipeline / test (push) Failing after 2m15s
CI/CD Pipeline / deploy-internal (push) Has been skipped

This commit is contained in:
chris committed 2026-08-28 20:01:12 -04:00
1 parent c3e2cf5ed3
commit fe31376f67
2 files changed
+82

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { buildCategoryTree, CategoryNode } from './tree.utility.ts';
describe('Tree Utility - buildCategoryTree()', () => {
it('should transform a flat list of ltree rows into a nested JSON structure', () => {
const flatNodes: CategoryNode[] = [
{ id: 1, name: 'Root', path: 'Top' },
{ id: 3, name: 'Astronomy', path: 'Top.Science.Astronomy' },
{ id: 2, name: 'Science', path: 'Top.Science' },
{ id: 4, name: 'Gardening', path: 'Top.Hobbies.Gardening' },
{ id: 5, name: 'Hobbies', path: 'Top.Hobbies' }
];
const tree = buildCategoryTree(flatNodes);
// Verify root nodes count
assert.equal(tree.length, 1);
assert.equal(tree[0].name, 'Root');
// Verify second level ("Science" and "Hobbies")
const rootChildren = tree[0].children;
assert.equal(rootChildren.length, 2);
const scienceNode = rootChildren.find(c => c.name === 'Science');
const hobbiesNode = rootChildren.find(c => c.name === 'Hobbies');
assert.ok(scienceNode);
assert.ok(hobbiesNode);
// Verify third level deeply nested child ("Astronomy")
assert.equal(scienceNode.children.length, 1);
assert.equal(scienceNode.children[0].name, 'Astronomy');
});
});
+47
View File
@@ -0,0 +1,47 @@
export interface CategoryNode {
id: number;
name: string;
path: string;
}
export interface TreeResponseNode {
id: number;
name: string;
path: string;
children: TreeResponseNode[];
}
export function buildCategoryTree(nodes: CategoryNode[]): TreeResponseNode[] {
const tree: TreeResponseNode[] = [];
const nodeMap = new Map<string, TreeResponseNode>();
// Sort nodes by path depth (nlevel) implicitly by splitting path strings
// This guarantees parents are always processed before their children
const sortedNodes = [...nodes].sort((a, b) => a.path.split('.').length - b.path.split('.').length);
// Build the map and establish relationships
for (const node of sortedNodes) {
const treeNode: TreeResponseNode = { ...node, children: [] };
nodeMap.set(node.path, treeNode);
const pathParts = node.path.split('.');
if (pathParts.length === 1) {
// Root level node (e.g., "Top")
tree.push(treeNode);
} else {
// Find parent path by dropping the last element (e.g., "Top.Science" -> "Top")
const parentPath = pathParts.slice(0, -1).join('.');
const parentNode = nodeMap.get(parentPath);
if (parentNode) {
parentNode.children.push(treeNode);
} else {
// Fallback: If parent isn't in the dataset, treat it as an orphan root
tree.push(treeNode);
}
}
}
return tree;
}