operational-decision-tree
Advanced tools
Comparing version 3.0.1 to 4.0.0
@@ -1,78 +0,82 @@ | ||
{ | ||
"decisions": [ | ||
[{ | ||
"property": "age", | ||
"operation": ">", | ||
"value": 40 | ||
}] | ||
], | ||
"branches": [ | ||
{ | ||
"runOnHit": [ | ||
"howOld" | ||
], | ||
"decisions": [ | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 30 | ||
}], | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 50 | ||
}], | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 90 | ||
}], | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 100 | ||
}] | ||
], | ||
"branches": [ | ||
{"result": "Leaf A: Aged under 40 and random less than 30"}, | ||
{"result": "Leaf B: Aged under 40 and random between 30 and 49"}, | ||
{"result": "Leaf C: Aged under 40 and random between 50 and 89"}, | ||
{"result": "Leaf D: Aged under 40 and random between 90 and 99"} | ||
] | ||
}, | ||
{ | ||
"decisions": [ | ||
[{ | ||
"branches": [{ | ||
"decisions": [{ | ||
"property": "age", | ||
"operation": ">", | ||
"value": 40 | ||
}], | ||
"runOnHit":[], | ||
"node": { | ||
"branches": [{ | ||
"decisions": | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 30 | ||
}], | ||
"runOnHit":["howOld"], | ||
"node": [{"result": "Leaf A: Aged under 40 and random less than 30"}] | ||
}, | ||
{ | ||
"decisions": | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 50 | ||
}], | ||
"runOnHit":[], | ||
"node": [{"result": "Leaf B: Aged under 40 and random between 30 and 49"}] | ||
}, | ||
{ | ||
"decisions": | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 90 | ||
}], | ||
"runOnHit":[], | ||
"node": [{"result": "Leaf C: Aged under 40 and random between 50 and 89"}] | ||
}, | ||
{ | ||
"decisions": | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 100 | ||
}], | ||
"runOnHit":[], | ||
"node": [{"result": "Leaf D: Aged under 40 and random between 90 and 99"}] | ||
} | ||
]} | ||
}, | ||
{ | ||
"decisions": [{ | ||
"property": "nationality", | ||
"operation": "in", | ||
"value": ["US", "CA"] | ||
}] | ||
], | ||
"branches": [ | ||
{ | ||
"decisions": [ | ||
[ | ||
{ | ||
}], | ||
"node": { | ||
"branches": [{ | ||
"decisions": [ | ||
{ | ||
"property": "age", | ||
"operation": "<", | ||
"value": 60 | ||
}, | ||
{ | ||
}, | ||
{ | ||
"property": "gender", | ||
"operation": "==", | ||
"value": "male" | ||
} | ||
] | ||
} | ||
], | ||
"branches": [ | ||
{"result": "Leaf E: Over 60 or female, and not from US"}, | ||
{"result": "Leaf F: Male under 60 and not from US"} | ||
] | ||
}, | ||
{"result": "Leaf G: Over 40 and from US"} | ||
] | ||
"node": [{"result": "Leaf E: Over 60 or female, and not from US"}] | ||
}, | ||
{ | ||
"node": [{"result": "Leaf G"}] | ||
}] | ||
} | ||
] | ||
}] | ||
} | ||
142
lib/index.js
@@ -31,33 +31,42 @@ | ||
var resolvedIndex = 0 | ||
this.runHits(node, subjectData) | ||
if (node.decisions) { | ||
async.forEachOf(node.decisions, function (comparisons, index, callback) { | ||
if (resolved) { | ||
// run prepare for each decision as it is encountered | ||
async.forEachOf(node.branches, function (nodeBranch, index, callback) { | ||
if (resolved) { | ||
callback(null) | ||
} else { | ||
self.runHits(nodeBranch, subjectData) | ||
self.prepareSubject(nodeCount, node, nodeBranch, subjectData, populated, function (err) { | ||
if (err) return cb(err) | ||
var result = self.runDecisions(nodeCount, nodeBranch.decisions, subjectData, populated) | ||
if (result) { | ||
resolved = true | ||
} else { | ||
resolvedIndex++ | ||
} | ||
callback(null) | ||
}) | ||
} | ||
}, function (err) { | ||
if (err) return cb(err) | ||
var nextNode = node.branches[resolvedIndex] | ||
if (nextNode.node) { | ||
if (nextNode.node.branches) { | ||
self.runNode(nodeCount, nextNode.node, subjectData, populated, cb) | ||
} else { | ||
self.prepareSubject(nodeCount, node, node.decisions, comparisons, subjectData, populated, function (err) { | ||
if (err) return cb(err) | ||
var result = self.runComparisons(nodeCount, comparisons, subjectData, populated) | ||
if (result) { | ||
resolved = true | ||
} else { | ||
resolvedIndex++ | ||
} | ||
callback(null) | ||
}) | ||
// we have a final node | ||
return cb(null, nextNode.node) | ||
} | ||
}, function (err) { | ||
if (err) return cb(err) | ||
self.handleDecision(nodeCount, node, subjectData, populated, resolvedIndex, cb) | ||
}) | ||
} else { | ||
cb(null, node, populated) | ||
} | ||
} else { | ||
// we have a final node | ||
return cb(null, nextNode) | ||
} | ||
}) | ||
} | ||
DecisionTree.prototype.runComparisons = function (nodeCount, comparisons, subjectData, populated) { | ||
DecisionTree.prototype.runDecisions = function (nodeCount, decisions, subjectData, populated) { | ||
var self = this | ||
for (var i in comparisons) { | ||
var comparison = comparisons[i] | ||
var result = this.compare(nodeCount, comparison.property, comparison, subjectData, populated) | ||
for (var i in decisions) { | ||
var decision = decisions[i] | ||
var result = this.compare(nodeCount, decision.property, decision, subjectData, populated) | ||
if (!result) { | ||
@@ -70,9 +79,9 @@ return false | ||
DecisionTree.prototype.runHits = function (node, subjectData) { | ||
DecisionTree.prototype.runHits = function (nodeBranch, subjectData) { | ||
var self = this | ||
if (node.runOnHit && this.opts.runOnHit) { | ||
for (var i in node.runOnHit) { | ||
var fn = node.runOnHit[i] | ||
if (nodeBranch.runOnHit && this.opts.runOnHit) { | ||
for (var i in nodeBranch.runOnHit) { | ||
var fn = nodeBranch.runOnHit[i] | ||
if (this.opts.runOnHit[fn]) { | ||
this.opts.runOnHit[fn](node, subjectData) | ||
this.opts.runOnHit[fn](nodeBranch, subjectData) | ||
} | ||
@@ -85,23 +94,27 @@ } | ||
DecisionTree.prototype.prepareSubject = function (nodeCount, node, decisions, comparisons, subjectData, populated, cb) { | ||
DecisionTree.prototype.prepareSubject = function (nodeCount, node, nodeBranch, subjectData, populated, cb) { | ||
var self = this | ||
async.each(comparisons, function (comparison, callback) { | ||
if (populated && populated[nodeCount] && populated[nodeCount].hasOwnProperty(comparison.property)) { | ||
callback(null) | ||
} else if (self.opts.populateFunctions && self.opts.populateFunctions[comparison.property]) { | ||
self.opts.populateFunctions[comparison.property](node, comparisons, comparison, subjectData, function (err, result) { | ||
if (err) return cb(err) | ||
if (!populated[nodeCount]) { | ||
populated[nodeCount] = {} | ||
} | ||
populated[nodeCount][comparison.property] = result | ||
if (nodeBranch.decisions) { | ||
async.each(nodeBranch.decisions, function (decision, callback) { | ||
if (populated && populated[nodeCount] && populated[nodeCount].hasOwnProperty(decision.property)) { | ||
callback(null) | ||
} else if (self.opts.populateFunctions && self.opts.populateFunctions[decision.property]) { | ||
self.opts.populateFunctions[decision.property](node, nodeBranch, decision, subjectData, function (err, result) { | ||
if (err) return cb(err) | ||
if (!populated[nodeCount]) { | ||
populated[nodeCount] = {} | ||
} | ||
populated[nodeCount][decision.property] = result | ||
return callback(null) | ||
}) | ||
} else { | ||
return callback(null) | ||
}) | ||
} else { | ||
return callback(null) | ||
} | ||
}, function (err) { | ||
if (err) return err | ||
} | ||
}, function (err) { | ||
if (err) return err | ||
return cb(null) | ||
}) | ||
} else { | ||
return cb(null) | ||
}) | ||
} | ||
} | ||
@@ -119,3 +132,6 @@ | ||
case ">": | ||
if (val > comparison.value) { | ||
if (typeof(val) === 'undefined' || val === null) { | ||
val = 0 | ||
} | ||
if (Number(val) > Number(comparison.value)) { | ||
result = 1; | ||
@@ -125,3 +141,6 @@ } | ||
case ">=": | ||
if (val >= comparison.value) { | ||
if (typeof(val) === 'undefined' || val === null) { | ||
val = 0 | ||
} | ||
if (Number(val) >= Number(comparison.value)) { | ||
result = 1; | ||
@@ -131,3 +150,6 @@ } | ||
case "<": | ||
if (val < comparison.value) { | ||
if (typeof(val) === 'undefined' || val === null) { | ||
val = 0 | ||
} | ||
if (Number(val) < Number(comparison.value)) { | ||
result = 1; | ||
@@ -137,3 +159,6 @@ } | ||
case "<=": | ||
if (val <= comparison.value) { | ||
if (typeof(val) === 'undefined' || val === null) { | ||
val = 0 | ||
} | ||
if (Number(val) <= Number(comparison.value)) { | ||
result = 1; | ||
@@ -185,18 +210,10 @@ } | ||
} | ||
console.log("COMPARING: "+val+" "+comparison.operation+" "+comparison.value) | ||
return result | ||
} | ||
DecisionTree.prototype.handleDecision = function (nodeCount, node, subjectData, populated, resolvedIndex, cb) { | ||
if (node.branches) { | ||
if (!node.branches[resolvedIndex]) { | ||
return cb("result branch does not exist") | ||
} | ||
this.runNode(nodeCount, node.branches[resolvedIndex], subjectData, populated, cb) | ||
} else { | ||
return cb('Node with decisions has no branches! nodeCount:'+nodeCount+' resolvedIndex:'+resolvedIndex) | ||
} | ||
} | ||
// utility functions | ||
/* | ||
DecisionTree.prototype.countDecisions = function (treeData, finished) { | ||
@@ -243,2 +260,3 @@ var decisionCount = {} | ||
} | ||
*/ | ||
@@ -245,0 +263,0 @@ DecisionTree.prototype.incrementDecisionCount = function (decisionCount, decisionName) { |
{ | ||
"name": "operational-decision-tree", | ||
"version": "3.0.1", | ||
"version": "4.0.0", | ||
"description": "A Decision Tree executor that uses decision modules to decide pathways. Can accept binary or arbitrary decision modules.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
176
README.md
@@ -7,11 +7,53 @@ # Operational-Decision-Tree # | ||
## What it's not ## | ||
## What it is ## | ||
ODT is not designed to be a machine learning decision tree. By all means though, use it how you want. | ||
ODT is a decision tree executor designed to run decision trees that are human-built and managed. You give it a tree object and a subject object. It then runs the tree against the subject and passes back the leaf node along with any data generated along the way. | ||
## What it is ## | ||
Usage looks like this: | ||
```js | ||
var ODT = require('operational-decision-tree') | ||
ODT is a decision tree executor designed to run decision trees that are human-built and managed. Hence, it has features targetted at reducing the total number of nodes needed to achieve the desired leaf nodes. | ||
var treeData = require('./binary-tree.json') | ||
For each condition, ODT compares the subject data to the tree data based on the "operation" parameter you pass in the tree. Supported operations are: | ||
var person = { | ||
name: "Bob", | ||
age: 37, | ||
nationality: 'US' | ||
} | ||
var DecisionTree = new ODT({ | ||
makeDecision: {}, | ||
runOnHit: {} | ||
}) | ||
DecisionTree.run(treeData, person, function (err, resultBranchNode) { | ||
if (err) console.error(err) | ||
console.log("Result", resultBranchNode) | ||
}) | ||
``` | ||
Each node can have the following properties: | ||
```js | ||
var node = { | ||
"decisions": [], // the decisions that will be run for this node in order to produce an output branch | ||
"branches": [], // each branch is another node | ||
"runOnHit": [], // a list of function names to run when this node is hit. | ||
"makeDecision": "" // the name of a function to run instead of the built in comparison function | ||
} | ||
``` | ||
For each node: | ||
1. check if 'makeDecision' is set. | ||
* true: run a function by this name from corresponding opts object | ||
* false: go to #2 | ||
2. ODT runs each comparison in the list and: | ||
* if all comparisons are true, then the branch with the same index as this decision is chosen. | ||
* if the chosen branch has the 'decisions' property or the 'makeDecisions' property, then return to step 1 and use this branch as the node to test | ||
* if not, then the top level callback is triggered with this branch node as the result | ||
* if any are false, then return to step 2 and test the next decision | ||
Once we hit a branch node that doesn't have either the 'decisions' property or the 'makeDecision' property, then the callback is triggered with the branch node as the result. | ||
For each comparison, ODT compares the subject data to the tree data based on the "operation" parameter you pass in the tree. Supported operations are: | ||
* > subject value is greater than tree comparison value | ||
@@ -28,12 +70,4 @@ * >= subject value is greater than or equal to tree comparison value | ||
Several things are required to use an ODT: | ||
* The executor (this package) | ||
* The tree data (provided by you) | ||
* The decision maker (a default decision maker is included in this package, but you can insert your own if need be) | ||
The number of decisions should always be one less than the number of branches. In the event that all decisions come back false, the last branch is chosen. | ||
Depending on the tree data you provide, the executor supports any combination of the following: | ||
* Binary conditions (conditions that output either true or false) | ||
* Arbitrary conditions (conditions that output an integer. In other words, more than two outcome branchways are possible) | ||
* Multi-condition nodes (a single decision node can run multiple conditions) | ||
## Example ## | ||
@@ -44,37 +78,80 @@ | ||
{ | ||
"condition": { | ||
"name": "conditionAge", | ||
"property": "age", | ||
"comparison": { | ||
"decisions": [ | ||
[{ | ||
"property": "age", | ||
"operation": ">", | ||
"value": 40 | ||
} | ||
}, | ||
}] | ||
], | ||
"branches": [ | ||
{ | ||
"condition": { | ||
"name": "conditionAge", | ||
"property": "age", | ||
"comparison": { | ||
"operation": ">", | ||
"value": 20 | ||
} | ||
}, | ||
"runOnHit": [ | ||
"howOld" | ||
], | ||
"decisions": [ | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 30 | ||
}], | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 50 | ||
}], | ||
[{ | ||
"property": "random", | ||
"operation": "<", | ||
"value": 90 | ||
}] | ||
], | ||
"branches": [ | ||
{"result": "Leaf A: Aged between 0 and 20"}, | ||
{"result": "Leaf B: Aged between 21 and 39"} | ||
{"result": "Leaf A: Aged under 40 and random less than 30"}, | ||
{"result": "Leaf B: Aged under 40 and random between 30 and 49"}, | ||
{"result": "Leaf C: Aged under 40 and random between 50 and 89"}, | ||
{"result": "Leaf D: Aged under 40 and random between 90 and 99"} | ||
] | ||
}, | ||
{ | ||
"condition": { | ||
"name": "conditionCountry", | ||
"property": "nationality", | ||
"comparison": { | ||
"decisions": [ | ||
[{ | ||
"property": "nationality", | ||
"operation": "in", | ||
"value": ["US", "CA"] | ||
}] | ||
], | ||
"branches": [ | ||
{ | ||
"decisions": [ | ||
[ | ||
{ | ||
"property": "age", | ||
"operation": "<", | ||
"value": 60 | ||
}, | ||
{ | ||
"property": "gender", | ||
"operation": "==", | ||
"value": "male" | ||
} | ||
] | ||
], | ||
"branches": [ | ||
{"result": "Leaf E: Over 60 or female, and not from US"}, | ||
{"result": "Leaf F: Male under 60 and not from US"} | ||
] | ||
}, | ||
{ | ||
"makeDecision": "decideFinalBranch", | ||
"decisions": [ | ||
[{ | ||
"property": "percentage" | ||
}] | ||
], | ||
"branches": [ | ||
{"result": "Leaf G"}, | ||
{"result": "Leaf H"}, | ||
{"result": "Leaf I"} | ||
] | ||
} | ||
}, | ||
"branches": [ | ||
{"result": "Leaf C: Over 40 and not from North America"}, | ||
{"result": "Leaf D: Over 40 and from the North America"} | ||
] | ||
@@ -86,24 +163,1 @@ } | ||
```js | ||
var ODT = require('operational-decision-tree') | ||
var treeData = require('./binary-tree.json') | ||
var person = { | ||
name: "Bob", | ||
age: 37, | ||
nationality: 'US' | ||
} | ||
var DecisionTree = new ODT() | ||
DecisionTree.run(treeData, person, function (err, result) { | ||
if (err) console.error("ERROR", err) | ||
console.log("RESULT", result) | ||
}) | ||
``` | ||
## A Note About Multi-Condition Nodes ## | ||
Multi-condition nodes are useful for reducing the number of nodes needed for specific outcomes. They work well with binary trees, since the decision maker can resolve them with an && operation. However, when paired with arbitrary conditions, you may run into some problems depending on how your arbitrary conditions are setup. |
@@ -14,22 +14,50 @@ | ||
var subject = { | ||
value: 1 | ||
target: 1 | ||
} | ||
var condition = { | ||
name: 'conditionBinary', | ||
property: 'value', | ||
comparison: { | ||
operation: '>', | ||
value: 0 | ||
} | ||
var decisions = [{ | ||
property: 'target', | ||
operation: '>', | ||
value: 2 | ||
}] | ||
var result = DT.runDecisions(0, decisions, subject, {}) | ||
t.equal(result, false, 'Binary condition returns false') | ||
subject.target = 3 | ||
var result = DT.runDecisions(0, decisions, subject, {}) | ||
t.equal(result, true, 'Binary condition returns true') | ||
t.end() | ||
}) | ||
tape('Test Decision with Multiple Conditions', function (t) { | ||
var DT = new DTS() | ||
var subject = { | ||
testFirst: 1, | ||
testSecond: 1 | ||
} | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.equal(result, 1, 'Binary condition returns 1') | ||
subject.value = 0 | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.equal(result, 0, 'Binary condition returns 0') | ||
t.end() | ||
}) | ||
}) | ||
var decisions = [ | ||
{ | ||
property: 'testFirst', | ||
operation: '>', | ||
value: 0 | ||
}, | ||
{ | ||
property: 'testSecond', | ||
operation: '>', | ||
value: 2 | ||
} | ||
] | ||
var result = DT.runDecisions(0, decisions, subject, {}) | ||
t.equal(result, false, 'Multiple conditions return false') | ||
subject.testSecond = 3 | ||
var result = DT.runDecisions(0, decisions, subject, {}) | ||
t.equal(result, true, 'Multiple conditions returns true') | ||
t.end() | ||
}) | ||
@@ -44,17 +72,27 @@ | ||
var condition = { | ||
name: 'conditionArbitrary', | ||
property: 'random', | ||
comparisons: [ | ||
var node = { | ||
branches: [ | ||
{ | ||
operation: '<', | ||
value: 30 | ||
decisions: [{ | ||
property: 'random', | ||
operation: '<', | ||
value: 30 | ||
}], | ||
node: 'first' | ||
}, | ||
{ | ||
operation: '<', | ||
value: 50 | ||
decisions: [{ | ||
property: 'random', | ||
operation: '<', | ||
value: 50 | ||
}], | ||
node: 'second' | ||
}, | ||
{ | ||
operation: '<', | ||
value: 100 | ||
decisions: [{ | ||
property: 'random', | ||
operation: '<', | ||
value: 100 | ||
}], | ||
node: 'third' | ||
} | ||
@@ -64,10 +102,10 @@ ] | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.equal(result, 2, 'Arbitrary condition returns 2') | ||
DT.runNode(0, node, subject, {}, function (err, result) { | ||
t.equal(result, 'third', 'Node returns third node') | ||
subject.random = 35 | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.equal(result, 1, 'Arbitrary condition returns 1') | ||
DT.runNode(0, node, subject, {}, function (err, result) { | ||
t.equal(result, 'second', 'Node returns second node') | ||
subject.random = 5 | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.equal(result, 0, 'Arbitrary condition returns 0') | ||
DT.runNode(0, node, subject, {}, function (err, result) { | ||
t.equal(result, 'first', 'Node returns first node') | ||
t.end() | ||
@@ -79,59 +117,49 @@ }) | ||
tape('Test Condition With Opts', function (t) { | ||
var DT = new DTS() | ||
var subject = { | ||
somevalue: 1 | ||
} | ||
var condition = { | ||
name: 'conditionOpts', | ||
property: 'somevalue', | ||
comparison: { | ||
operation: '<', | ||
value: 2 | ||
} | ||
} | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.ok(result, 'Condition with opts returns true') | ||
subject.somevalue = 3 | ||
DT.testCondition(condition, subject, function (err, result) { | ||
t.notOk(result, 'Condition with opts returns false') | ||
t.end() | ||
}) | ||
}) | ||
}) | ||
tape('Test runNode', function (t) { | ||
tape('Test Deep Tree', function (t) { | ||
var DT = new DTS() | ||
var subject = { | ||
age: 37 | ||
random: 29, | ||
age: 16 | ||
} | ||
var node = { | ||
condition: { | ||
name: "conditionAge", | ||
property: 'age', | ||
comparison: { | ||
operation: ">", | ||
value: 20 | ||
} | ||
}, | ||
branches: [ | ||
{ | ||
result: "Leaf A: Aged under 20" | ||
}, | ||
{ | ||
result: "Leaf B: Aged 21 or over" | ||
} | ||
] | ||
{ | ||
decisions: [{ | ||
property: 'random', | ||
operation: '<', | ||
value: 30 | ||
}], | ||
node: { | ||
branches: [ | ||
{ | ||
decisions: [{ | ||
property: 'age', | ||
operation: '<', | ||
value: 18 | ||
}], | ||
node: 'underage' | ||
}, | ||
'adult' | ||
] | ||
} | ||
}, | ||
{ | ||
decisions: [{ | ||
property: 'random', | ||
operation: '<', | ||
value: 50 | ||
}], | ||
node: 'second' | ||
} | ||
] | ||
} | ||
DT.runNode(node, subject, function (err, result) { | ||
t.ok(result, 'Leaf B: Aged 21 or over') | ||
subject.age = 8 | ||
DT.runNode(node, subject, function (err, result) { | ||
t.ok(result, 'Leaf A: Aged under 20') | ||
DT.runNode(0, node, subject, {}, function (err, result) { | ||
t.equal(result, 'underage', 'Returns deep node') | ||
subject.age = 21 | ||
DT.runNode(0, node, subject, {}, function (err, result) { | ||
t.equal(result, 'adult', 'Returns simple node') | ||
t.end() | ||
@@ -142,38 +170,2 @@ }) | ||
tape('Test defaultDecider', function (t) { | ||
var DT = new DTS() | ||
var result | ||
result = DT.defaultDecider(true) | ||
t.equal(result, 1, 'Decider properly handles "true"') | ||
result = DT.defaultDecider(false) | ||
t.equal(result, 0, 'Decider properly handles "false"') | ||
result = DT.defaultDecider(1) | ||
t.equal(result, 1, 'Decider properly handles "1"') | ||
result = DT.defaultDecider(0) | ||
t.equal(result, 0, 'Decider properly handles "0"') | ||
result = DT.defaultDecider(2) | ||
t.equal(result, 2, 'Decider properly handles "2"') | ||
result = DT.defaultDecider('Oops') | ||
t.equal(result instanceof Error, new Error() instanceof Error, 'Decider properly returns an error on strings') | ||
t.end() | ||
}) | ||
tape('Test countConditions', function (t) { | ||
var DT = new DTS() | ||
DT.countConditions(sampleTree, function(err, conditionCount) { | ||
t.equal(1, conditionCount.conditionPercent, 'countConditions returned the correct number of "conditionPercent"') | ||
t.equal(3, conditionCount.conditionAsync, 'countConditions returned the correct number of "conditionAsync"') | ||
t.end() | ||
}) | ||
}) | ||
20168
564
160