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