mirror of https://github.com/abpframework/abp.git
3 changed files with 120 additions and 0 deletions
@ -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<ModelA>[]; |
|||
} |
|||
|
|||
interface ModelA { |
|||
id: 1; |
|||
pid: null; |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
export class TreeNode<T extends any> { |
|||
children: TreeNode<T>[] = []; |
|||
isLeaf = true; |
|||
|
|||
constructor(props: T) { |
|||
Object.assign(this, props); |
|||
} |
|||
} |
|||
|
|||
export function createTreeFromList<T extends object, R extends BranchOrLeaf<T>>( |
|||
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<typeof valueMapper>[] = []; |
|||
|
|||
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<T extends object, R extends BranchOrLeaf<T>>( |
|||
list: T[], |
|||
keySelector: (item: T) => NodeKey, |
|||
valueMapper = (item: T) => new TreeNode(item) as R, |
|||
) { |
|||
const map = new Map<ReturnType<typeof keySelector>, ReturnType<typeof valueMapper>>(); |
|||
list.forEach(row => map.set(keySelector(row), valueMapper(row))); |
|||
return map; |
|||
} |
|||
|
|||
type NodeKey = number | string | Symbol; |
|||
|
|||
interface BranchOrLeaf<T> { |
|||
children: BranchOrLeaf<T>[]; |
|||
isLeaf: boolean; |
|||
} |
|||
Loading…
Reference in new issue