UNPKG

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
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([]); }); });