From ed41a884bc38c5eb0a8f3531f0c899d7b6082ae3 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 16 Jun 2020 11:25:09 +0300 Subject: [PATCH] feat: add tree utils to core --- .../core/src/lib/tests/tree-utils.spec.ts | 68 +++++++++++++++++++ .../packages/core/src/lib/utils/index.ts | 1 + .../packages/core/src/lib/utils/tree-utils.ts | 51 ++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts new file mode 100644 index 0000000000..454c1c6891 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/tree-utils.spec.ts @@ -0,0 +1,68 @@ +import { createTreeFromList, TreeNode } from '../utils/tree-utils'; + +const LIST_1 = [ + { id: 1, pid: null }, + { id: 2, pid: 1 }, +]; +const LIST_2 = [ + { id: 1, pid: null }, + { id: 2, pid: 1 }, + { id: 3, pid: 1 }, +]; +const LIST_3 = [ + { id: 1, pid: null }, + { id: 2, pid: 1 }, + { id: 3, pid: 2 }, +]; +const TREE_1 = [ + { id: 1, pid: null, isLeaf: false, children: [{ id: 2, pid: 1, isLeaf: true, children: [] }] }, +]; +const TREE_2 = [ + { + id: 1, + pid: null, + isLeaf: false, + children: [ + { id: 2, pid: 1, isLeaf: true, children: [] }, + { id: 3, pid: 1, isLeaf: true, children: [] }, + ], + }, +]; +const TREE_3 = [ + { + id: 1, + pid: null, + isLeaf: false, + children: [ + { id: 2, pid: 1, isLeaf: false, children: [{ id: 3, pid: 2, isLeaf: true, children: [] }] }, + ], + }, +]; +describe('Tree Utils', () => { + describe('createTreeFromList', () => { + test.each` + list | expected + ${LIST_1} | ${TREE_1} + ${LIST_2} | ${TREE_2} + ${LIST_3} | ${TREE_3} + `('should return $expected when given $list', ({ list, expected }: TestCreateTreeFromList) => { + expect( + createTreeFromList( + list, + x => x.id, + x => x.pid, + ), + ).toEqual(expected); + }); + }); +}); + +interface TestCreateTreeFromList { + list: ModelA[]; + expected: TreeNode[]; +} + +interface ModelA { + id: 1; + pid: null; +} diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index ce89b4dddb..6f6136b195 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -9,3 +9,4 @@ export * from './localization-utils'; export * from './number-utils'; export * from './route-utils'; export * from './rxjs-utils'; +export * from './tree-utils'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts new file mode 100644 index 0000000000..86844a4375 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts @@ -0,0 +1,51 @@ +export class TreeNode { + children: TreeNode[] = []; + isLeaf = true; + + constructor(props: T) { + Object.assign(this, props); + } +} + +export function createTreeFromList>( + list: T[], + keySelector: (item: T) => NodeKey, + parentKeySelector: (item: T) => NodeKey, + valueMapper = (item: T) => new TreeNode(item) as R, +) { + const map = createMapFromList(list, keySelector, valueMapper); + const tree: ReturnType[] = []; + + list.forEach(row => { + const id = keySelector(row); + const parentId = parentKeySelector(row); + const node = map.get(id); + + if (parentId) { + const parent = map.get(parentId); + parent.children.push(node); + parent.isLeaf = false; + } else { + tree.push(node); + } + }); + + return tree; +} + +export function createMapFromList>( + list: T[], + keySelector: (item: T) => NodeKey, + valueMapper = (item: T) => new TreeNode(item) as R, +) { + const map = new Map, ReturnType>(); + list.forEach(row => map.set(keySelector(row), valueMapper(row))); + return map; +} + +type NodeKey = number | string | Symbol; + +interface BranchOrLeaf { + children: BranchOrLeaf[]; + isLeaf: boolean; +}