simple-tree-utils
Version:
Simple Tree Utils is the library to convert and manipulate with tree-like structures.
477 lines (431 loc) • 18.6 kB
text/typescript
import {beforeEach, describe, expect, it} from 'vitest'
import {TreeUtils} from "../src";
function getMockedList(parentCustomId: any = null): any[] {
return [
{customId: 1, parentCustomId, name: 'Node 1'},
{customId: 2, parentCustomId, name: 'Node 2'},
{customId: 3, parentCustomId: 1, name: 'Node 3'},
{customId: 4, parentCustomId: 1, name: 'Node 4'},
{customId: 5, parentCustomId: 2, name: 'Node 5'},
{customId: 6, parentCustomId: 3, name: 'Node 6'},
]
}
function getMockedTree(parentCustomId: any = null): any[] {
return [
{
customId: 1, parentCustomId, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []},
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
]
}
const TREE_MOCK_ITEMS2 = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []},
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
{customId: 6, parentCustomId: 2, name: 'Node 6', customChildren: []},
{customId: 7, parentCustomId: 2, name: 'Node 7', customChildren: []},
]
},
];
const TREE_MOCK_ITEMS_NO_CHILDREN = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [{customId: 6, parentCustomId: 2, name: 'Node 6'}]},
{customId: 4, parentCustomId: 1, name: 'Node 4'},
]
},
{customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [{customId: 5, parentCustomId: 2, name: 'Node 5'}]},
];
const treeUtils = new TreeUtils({
idProp: 'customId',
parentIdProp: 'parentCustomId',
childrenProp: 'customChildren',
});
describe('Tree utils methods', () => {
let mock: any;
beforeEach(() => {
mock = JSON.parse(JSON.stringify(getMockedTree(null)));
})
it('should setup converter with defaults when parameter is not provided', () => {
const treeUtils = new TreeUtils();
expect(treeUtils.list2Tree([{id: 1, parentId: null, name: 'Node 1'}]))
.toEqual([{id: 1, parentId: null, name: 'Node 1', children: []}]);
});
it('should convert list to tree', () => {
expect(treeUtils.list2Tree(getMockedList())).toEqual(getMockedTree());
});
it('should convert tree to list', () => {
expect(treeUtils.tree2List(getMockedTree()).sort((a: any, b: any) => a.customId - b.customId)).toEqual(getMockedList());
});
it('should convert tree to list when some nodes does not contains children prop', () => {
expect(treeUtils.tree2List(TREE_MOCK_ITEMS_NO_CHILDREN).sort((a: any, b: any) => a.customId - b.customId)).toEqual(getMockedList());
});
it('should get node by given customId', () => {
expect(treeUtils.get(getMockedTree(), 6)).toEqual({customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []});
});
it('should not fail, when children prop is missing', () => {
const input = [
{customId: 1, parentCustomId: null, name: 'Node 1'}
]
expect(treeUtils.get(input, 6)).toBeNull();
expect(treeUtils.getParent(input, 6)).toBeNull();
});
it('should delete node by given customId', () => {
const deleted = treeUtils.delete(mock, 6);
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: []
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
];
expect(deleted).toEqual({customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []});
expect(mock).toEqual(out);
});
it('should delete node returns null when not found', () => {
const deleted = treeUtils.delete(mock, 100);
expect(deleted).toEqual(null);
});
it('should delete node when there is no children property', () => {
const input = [{customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6'}]},
{customId: 4, parentCustomId: 1, name: 'Node 4'}]},
];
const deleted = treeUtils.deleteBy(input, item => item.customId === 6);
expect(deleted).toEqual([{customId: 6, parentCustomId: 3, name: 'Node 6'}]);
});
it('should delete node by given callback', () => {
const deleted = treeUtils.deleteBy(mock, item => item.customId === 5 || item.customId === 6);
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: []
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{customId: 2, parentCustomId: null, name: 'Node 2', customChildren: []},
];
expect(deleted).toEqual([
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []},
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]);
expect(mock).toEqual(out);
});
it('should iterate over each node', () => {
treeUtils.forEach(mock, item => item.name = `I${item.name}`);
const out = [
{
customId: 1, parentCustomId: null, name: 'INode 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'INode 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'INode 6', customChildren: []},
]
},
{customId: 4, parentCustomId: 1, name: 'INode 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'INode 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'INode 5', customChildren: []},
]
},
];
expect(mock).toEqual(out);
});
it('should compute paths for nodes', () => {
treeUtils.computePaths(mock, 'name');
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', path: '/', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', path: '/Node 1/', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', path: '/Node 1/Node 3/', customChildren: []},
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', path: '/Node 1/', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', path: '/', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', path: '/Node 2/', customChildren: []},
]
},
];
expect(mock).toEqual(out);
});
it('should add node to tree as first child of given node', () => {
treeUtils.addUnshift(mock, 1, {customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []});
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []},
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
]
expect(mock).toEqual(out);
});
it('should add node to parent with given customId', () => {
treeUtils.add(mock, 4, {customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []});
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: [
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []}
]},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
]
expect(mock).toEqual(out);
});
it('should add multiple nodes to parent with given customId', () => {
treeUtils.add(mock, 4,
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []},
{customId: 8, parentCustomId: 4, name: 'Node 8', customChildren: []},
);
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: [
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []},
{customId: 8, parentCustomId: 4, name: 'Node 8', customChildren: []},
]},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
]
expect(mock).toEqual(out);
});
it('should add node to root', () => {
treeUtils.add(mock, null, {customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []});
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []}
]
expect(mock).toEqual(out);
});
it('should add multiple nodes to root', () => {
treeUtils.add(mock, null,
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []},
{customId: 8, parentCustomId: 4, name: 'Node 8', customChildren: []},
);
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5', customChildren: []},
]
},
{customId: 7, parentCustomId: 4, name: 'Node 7', customChildren: []},
{customId: 8, parentCustomId: 4, name: 'Node 8', customChildren: []}
]
expect(mock).toEqual(out);
});
it('should edit node of given customId by given data', () => {
treeUtils.edit(mock, 5, {customId: 5, parentCustomId: 2, name: 'Node 5 edited', customChildren: []});
const out = [
{
customId: 1, parentCustomId: null, name: 'Node 1', customChildren: [
{
customId: 3, parentCustomId: 1, name: 'Node 3', customChildren: [
{customId: 6, parentCustomId: 3, name: 'Node 6', customChildren: []}
]
},
{customId: 4, parentCustomId: 1, name: 'Node 4', customChildren: []},
]
},
{
customId: 2, parentCustomId: null, name: 'Node 2', customChildren: [
{customId: 5, parentCustomId: 2, name: 'Node 5 edited', customChildren: []},
]
},
]
expect(mock).toEqual(out);
});
it('should find parent of node', () => {
expect(treeUtils.getParent(mock, 6).customId).toBe(3);
expect(treeUtils.getParent(mock, 1)).toBeNull();
});
it('should get ancestors of node', () => {
expect(treeUtils.getAncestors(mock, 6).map((item: any) => item.customId)).toEqual([1, 3]);
expect(treeUtils.getAncestors(mock, 4).map((item: any) => item.customId)).toEqual([1]);
expect(treeUtils.getAncestors(mock, 5).map((item: any) => item.customId)).toEqual([2]);
expect(treeUtils.getAncestors(mock, 1).map((item: any) => item.customId)).toEqual([]);
});
it('should get descendants of node', () => {
expect(treeUtils.getDescendants(mock, 1).map((item: any) => item.customId)).toEqual([3, 4, 6]);
expect(treeUtils.getDescendants(mock, 2).map((item: any) => item.customId)).toEqual([5]);
expect(treeUtils.getDescendants(mock, 5).map((item: any) => item.customId)).toEqual([]);
});
it('should not get descendants of nonexisting node', () => {
expect(treeUtils.getDescendants(mock, 666).map((item: any) => item.customId)).toEqual([]);
});
it('should find ALL nodes that match callback', () => {
expect(treeUtils.filter(mock, item => item.customId % 2 === 0).map((item: any) => item.customId)).toEqual([2, 4, 6]);
expect(treeUtils.filter(mock, item => item.customId % 3 === 0).map((item: any) => item.customId)).toEqual([3, 6]);
expect(treeUtils.filter(mock, item => item.name.includes('Node')).map((item: any) => item.customId)).toEqual([1, 2, 3, 4, 6, 5]);
});
it('should get neighbours of node', () => {
expect(treeUtils.getNeighbours(mock, 1).map((item: any) => item.customId)).toEqual([3, 4]);
expect(treeUtils.getNeighbours(mock, 3).map((item: any) => item.customId)).toEqual([1, 6]);
expect(treeUtils.getNeighbours(mock, 5).map((item: any) => item.customId)).toEqual([2]);
});
it('should get siblings of node', () => {
expect(treeUtils.getSiblings(mock, 1).map((item: any) => item.customId)).toEqual([]);
expect(treeUtils.getSiblings(mock, 3).map((item: any) => item.customId)).toEqual([4]);
expect(treeUtils.getSiblings(mock, 5).map((item: any) => item.customId)).toEqual([]);
});
it('should get leafs in subtree from node', () => {
expect(treeUtils.getLeafs(mock, 1).map((item: any) => item.customId)).toEqual([4, 6]);
expect(treeUtils.getLeafs(mock, 2).map((item: any) => item.customId)).toEqual([5]);
expect(treeUtils.getLeafs(mock, 3).map((item: any) => item.customId)).toEqual([6]);
expect(treeUtils.getLeafs(mock, 6).map((item: any) => item.customId)).toEqual([]);
});
it('should NOT get leafs for nonexisting node', () => {
expect(treeUtils.getLeafs(mock, 30).map((item: any) => item.customId)).toEqual([]);
});
it('should get size of tree', () => {
expect(treeUtils.getSize(mock, 1)).toBe(4);
expect(treeUtils.getSize(mock, 2)).toBe(2);
expect(treeUtils.getSize(mock, 3)).toBe(2);
});
it('should get size of tree', () => {
expect(treeUtils.getBreath(mock, 1)).toBe(2);
expect(treeUtils.getBreath(mock, 2)).toBe(1);
expect(treeUtils.getBreath(mock, 3)).toBe(1);
expect(treeUtils.getBreath(mock, 6)).toBe(0);
});
it('should get depth of tree', () => {
expect(treeUtils.getDepth(mock, 1)).toBe(0);
expect(treeUtils.getDepth(mock, 4)).toBe(1);
expect(treeUtils.getDepth(mock, 6)).toBe(2);
});
it('should get level of tree', () => {
expect(treeUtils.getLevel(mock, 1)).toBe(1);
expect(treeUtils.getLevel(mock, 4)).toBe(2);
expect(treeUtils.getLevel(mock, 6)).toBe(3);
});
it('should get degree of tree', () => {
expect(treeUtils.getDegree(mock, 1)).toBe(2);
expect(treeUtils.getDegree(mock, 3)).toBe(1);
expect(treeUtils.getDegree(mock, 6)).toBe(0);
});
it('should get tree degree of tree', () => {
expect(treeUtils.getTreeDegree(TREE_MOCK_ITEMS2)).toBe(3);
});
it('should get nodes at specific level', () => {
expect(treeUtils.getNodesAtLevel(mock, 0).map((item: any) => item.customId)).toEqual([1, 2]);
expect(treeUtils.getNodesAtLevel(mock, 1).map((item: any) => item.customId)).toEqual([3, 4, 5]);
expect(treeUtils.getNodesAtLevel(mock, 2).map((item: any) => item.customId)).toEqual([6]);
});
it('should get width at specific level', () => {
expect(treeUtils.getWidth(mock, 0)).toBe(2);
expect(treeUtils.getWidth(mock, 1)).toBe(3);
expect(treeUtils.getWidth(mock, 2)).toBe(1);
});
it('should get height of node', () => {
expect(treeUtils.getHeight(mock, 1)).toBe(2);
expect(treeUtils.getHeight(mock, 2)).toBe(1);
expect(treeUtils.getHeight(mock, 3)).toBe(1);
expect(treeUtils.getHeight(mock, 4)).toBe(0);
expect(treeUtils.getHeight(mock, 5)).toBe(0);
expect(treeUtils.getHeight(mock, 6)).toBe(0);
});
it('should get distance between 2 nodes', () => {
expect(treeUtils.getDistance(mock, 1, 2)).toBe(-1);
expect(treeUtils.getDistance(mock, 3, 4)).toBe(2);
expect(treeUtils.getDistance(mock, 4, 6)).toBe(3);
expect(treeUtils.getDistance(mock, 2, 5)).toBe(1);
expect(treeUtils.getDistance(mock, 1, 1)).toBe(0);
});
it('should get path nodes of node', () => {
expect(treeUtils.getPathNodes(mock, 6).map((item: any) => item.customId)).toEqual([1, 3]);
expect(treeUtils.getPathNodes(mock, 4).map((item: any) => item.customId)).toEqual([1]);
expect(treeUtils.getPathNodes(mock, 5).map((item: any) => item.customId)).toEqual([2]);
expect(treeUtils.getPathNodes(mock, 1).map((item: any) => item.customId)).toEqual([]);
});
});