-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Tree): implement tree structure w/ tests
This commit implements a Tree class on the server for constructing trees out of accounts, units, or other tree structures. The only requirement is that the objects have references pointing them to their parents.
- Loading branch information
Showing
3 changed files
with
166 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/** | ||
* @class Tree | ||
* | ||
* @description | ||
* This file contains the generic class definition of a tree. A tree is defined | ||
* as an array of JSON objects having a parent key referring to another member | ||
* of the array. The only exception is the root node, which does not need to be | ||
* in the tree. | ||
*/ | ||
const _ = require('lodash'); | ||
|
||
/** | ||
* @function buildTreeFromArray | ||
* | ||
* @description | ||
* This function makes a tree data structure from a properly formatted array. | ||
*/ | ||
function buildTreeFromArray(nodes, parentId, parentKey) { | ||
// recursion base-case: return nothing if empty array | ||
if (nodes.length === 0) { return null; } | ||
|
||
// find nodes which are the children of parentId | ||
const children = nodes.filter(node => node[parentKey] === parentId); | ||
|
||
// recurse - for each child node, compute their child-trees using the same | ||
// buildTreeFromArray() command | ||
children.forEach(node => { | ||
node.children = buildTreeFromArray(nodes, node.id, parentKey); | ||
}); | ||
|
||
// return the list of children | ||
return children; | ||
} | ||
|
||
/** | ||
* @function flatten | ||
* | ||
* @description | ||
* Operates on constructed trees which have "children" attributes holding all | ||
* child nodes. It computes the depth of the node and affixes it to the child | ||
* node. This function is recursive. | ||
* | ||
* @param {Array} tree - tree structure created by the tree constructor | ||
* @param {Number} depth - depth attribute | ||
* @param {Boolen} pruneChildren - instructs the function to remove children | ||
*/ | ||
function flatten(tree, depth, pruneChildren = true) { | ||
let currentDepth = (Number.isNaN(depth) || _.isUndefined(depth)) ? -1 : depth; | ||
currentDepth += 1; | ||
|
||
return tree.reduce((array, node) => { | ||
node.depth = currentDepth; | ||
const items = [node].concat(node.children ? | ||
flatten(node.children, currentDepth, pruneChildren) : []); | ||
|
||
if (pruneChildren) { delete node.children; } | ||
|
||
return array.concat(items); | ||
}, []); | ||
} | ||
|
||
|
||
class Tree { | ||
constructor(data = [], parentKey = 'parent', rootId = 0) { | ||
this._data = data; | ||
this._parentKey = parentKey; | ||
this._rootId = rootId; | ||
|
||
// build the tree with the provided root id and parentKey | ||
this._tree = buildTreeFromArray(_.cloneDeep(data), rootId, parentKey); | ||
} | ||
|
||
toArray() { | ||
return flatten(this._tree); | ||
} | ||
} | ||
|
||
module.exports = Tree; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
const Tree = require('../../server/lib/Tree'); | ||
const { expect } = require('chai'); | ||
|
||
function TreeUnitTests() { | ||
/* | ||
* This tree looks like this: | ||
* ROOT | ||
* / | \ | ||
* id:1 id:4 id:6 | ||
* / \ | ||
* id:2 id:3 | ||
* / | ||
* id:5 | ||
*/ | ||
const nodes = [{ | ||
id : 1, | ||
parent : 0, | ||
}, { | ||
id : 2, | ||
parent : 1, | ||
}, { | ||
id : 3, | ||
parent : 6, | ||
}, { | ||
id : 4, | ||
parent : 0, | ||
}, { | ||
id : 5, | ||
parent : 2, | ||
}, { | ||
id : 6, | ||
parent : 0, | ||
}]; | ||
|
||
it('#constructor() should populate private variables', () => { | ||
const tree = new Tree(nodes); | ||
expect(tree._data).to.deep.equal(nodes); | ||
expect(tree._tree).to.not.be.undefined; | ||
}); | ||
|
||
it('#constructor() should not have side-effects', () => { | ||
const cloned = JSON.parse(JSON.stringify(nodes)); | ||
const tree = new Tree(nodes); | ||
expect(tree._data).to.deep.equal(cloned); | ||
}); | ||
|
||
it('#constructor() node id:0 should have three childen', () => { | ||
const tree = new Tree(nodes)._tree; | ||
expect(tree).to.have.length(3); | ||
}); | ||
|
||
it('#constructor() nodes should all have "children" arrays', () => { | ||
const tree = new Tree(nodes)._tree; | ||
tree.forEach(node => { | ||
expect(node).to.have.property('children'); | ||
expect(node.children).to.be.an('array'); | ||
}); | ||
}); | ||
|
||
it('#constructor() node id:4 should not have any children', () => { | ||
const tree = new Tree(nodes)._tree; | ||
const node4 = tree[1]; | ||
expect(node4.id).to.equal(4); | ||
expect(node4.children).to.have.length(0); | ||
}); | ||
|
||
it('#constructor() node id:6 should have one child', () => { | ||
const tree = new Tree(nodes)._tree; | ||
const node6 = tree[2]; | ||
expect(node6.id).to.equal(6); | ||
expect(node6.children).to.have.length(1); | ||
}); | ||
|
||
it('#toArray() should return an array', () => { | ||
const array = new Tree(nodes).toArray(); | ||
expect(array).to.be.an('array'); | ||
}); | ||
|
||
it('#toArray() should populate the "depth" key on all nodes', () => { | ||
const array = new Tree(nodes).toArray(); | ||
array.forEach(node => { | ||
expect(node).to.have.property('depth'); | ||
}); | ||
}); | ||
} | ||
|
||
describe('Tree.js', TreeUnitTests); |