fable 3.0.149 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fable.js +31 -4
- package/dist/fable.min.js +2 -2
- package/dist/fable.min.js.map +1 -1
- package/package.json +2 -2
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +16 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js +5 -1
- package/source/services/Fable-Service-FilePersistence.js +8 -2
- package/source/services/Fable-Service-Math.js +165 -0
- package/source/services/Fable-Service-Utility.js +31 -0
- package/test/ExpressionParser_tests.js +34 -0
- package/test/FilePersistence_tests.js +14 -0
- package/test/Math_test.js +60 -0
- package/test/data/cities.json +9002 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fable",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "A service dependency injection, configuration and logging library.",
|
|
5
5
|
"main": "source/Fable.js",
|
|
6
6
|
"scripts": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"homepage": "https://github.com/stevenvelozo/fable",
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"quackage": "^1.0.
|
|
53
|
+
"quackage": "^1.0.38"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"async.eachlimit": "^0.5.2",
|
|
@@ -83,7 +83,13 @@
|
|
|
83
83
|
"Name": "Round",
|
|
84
84
|
"Address": "fable.Math.roundPrecise"
|
|
85
85
|
},
|
|
86
|
-
|
|
86
|
+
|
|
87
|
+
"cumulativesummation": {
|
|
88
|
+
"Name": "Count Set Elements in a Histogram or Value Map",
|
|
89
|
+
"Address": "fable.Math.cumulativeSummation"
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
"countsetelements": {
|
|
87
93
|
"Name": "Count Set Elements in a Histogram or Value Map",
|
|
88
94
|
"Address": "fable.Math.countSetElements"
|
|
89
95
|
},
|
|
@@ -93,6 +99,15 @@
|
|
|
93
99
|
"Address": "fable.Utility.getInternalValueByHash"
|
|
94
100
|
},
|
|
95
101
|
|
|
102
|
+
"aggregationhistogram": {
|
|
103
|
+
"Name": "Generate a Histogram by Exact Value Aggregation",
|
|
104
|
+
"Address": "fable.Math.histogramAggregationByExactValueFromInternalState"
|
|
105
|
+
},
|
|
106
|
+
"distributionhistogram": {
|
|
107
|
+
"Name": "Generate a Histogram Based on Value Distribution",
|
|
108
|
+
"Address": "fable.Math.histogramDistributionByExactValueFromInternalState"
|
|
109
|
+
},
|
|
110
|
+
|
|
96
111
|
"getvaluearray": {
|
|
97
112
|
"Name": "Get Value Array from Application State or Services (AppData, etc.)",
|
|
98
113
|
"Address": "fable.Utility.createValueArrayByHashParametersFromInternal"
|
|
@@ -204,7 +204,11 @@ class ExpressionParserSolver extends libExpressionParserOperationBase
|
|
|
204
204
|
delete tmpResults.fable;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
if (typeof(tmpSolverResultValue)
|
|
207
|
+
if (typeof(tmpSolverResultValue) === 'object')
|
|
208
|
+
{
|
|
209
|
+
return tmpSolverResultValue;
|
|
210
|
+
}
|
|
211
|
+
else if (typeof(tmpSolverResultValue) !== 'undefined')
|
|
208
212
|
{
|
|
209
213
|
return tmpSolverResultValue.toString();
|
|
210
214
|
}
|
|
@@ -24,6 +24,13 @@ class FableServiceFilePersistence extends libFableServiceBase
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
joinPath(...pPathArray)
|
|
27
|
+
{
|
|
28
|
+
// TODO: Fix anything that's using this before changing this to the new true node join
|
|
29
|
+
// return libPath.join(...pPathArray);
|
|
30
|
+
return libPath.resolve(...pPathArray);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resolvePath(...pPathArray)
|
|
27
34
|
{
|
|
28
35
|
return libPath.resolve(...pPathArray);
|
|
29
36
|
}
|
|
@@ -35,8 +42,7 @@ class FableServiceFilePersistence extends libFableServiceBase
|
|
|
35
42
|
|
|
36
43
|
exists(pPath, fCallback)
|
|
37
44
|
{
|
|
38
|
-
let tmpFileExists = this.existsSync(pPath)
|
|
39
|
-
|
|
45
|
+
let tmpFileExists = this.existsSync(pPath);
|
|
40
46
|
return fCallback(null, tmpFileExists);
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -621,6 +621,171 @@ class FableServiceMath extends libFableServiceBase
|
|
|
621
621
|
return tmpCleanedObject;
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Make a histogram of representative counts for exact values (.tostring() is the keys to count)
|
|
626
|
+
* @param {Array} pValueSet
|
|
627
|
+
* @param {string} pValueAddress
|
|
628
|
+
*/
|
|
629
|
+
histogramDistributionByExactValue(pValueObjectSet, pValueAddress, pManifest)
|
|
630
|
+
{
|
|
631
|
+
if (!Array.isArray(pValueObjectSet))
|
|
632
|
+
{
|
|
633
|
+
return pValueObjectSet;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (!pValueAddress)
|
|
637
|
+
{
|
|
638
|
+
return {};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
let tmpHistogram = {};
|
|
642
|
+
for (let i = 0; i < pValueObjectSet.length; i++)
|
|
643
|
+
{
|
|
644
|
+
let tmpValue = this.fable.Utility.getValueByHash(pValueObjectSet[i], pValueAddress, pManifest).toString();
|
|
645
|
+
|
|
646
|
+
if (!(tmpValue in tmpHistogram))
|
|
647
|
+
{
|
|
648
|
+
tmpHistogram[tmpValue] = 0;
|
|
649
|
+
}
|
|
650
|
+
tmpHistogram[tmpValue] = tmpHistogram[tmpValue] + 1;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return tmpHistogram;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
histogramDistributionByExactValueFromInternalState(pValueObjectSetAddress, pValueAddress)
|
|
657
|
+
{
|
|
658
|
+
if (!pValueObjectSetAddress)
|
|
659
|
+
{
|
|
660
|
+
return {};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
let tmpValueObjectSet = this.fable.Utility.getInternalValueByHash(pValueObjectSetAddress);
|
|
664
|
+
return this.histogramDistributionByExactValue(tmpValueObjectSet, pValueAddress);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Make a histogram of representative counts for exact values (.tostring() is the keys to count)
|
|
669
|
+
* @param {Array} pValueSet
|
|
670
|
+
* @param {string} pValueAddress
|
|
671
|
+
*/
|
|
672
|
+
histogramAggregationByExactValue(pValueObjectSet, pValueAddress, pValueAmountAddress, pManifest)
|
|
673
|
+
{
|
|
674
|
+
if (!Array.isArray(pValueObjectSet))
|
|
675
|
+
{
|
|
676
|
+
return pValueObjectSet;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (!pValueAddress || !pValueAmountAddress)
|
|
680
|
+
{
|
|
681
|
+
return {};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
let tmpHistogram = {};
|
|
685
|
+
for (let i = 0; i < pValueObjectSet.length; i++)
|
|
686
|
+
{
|
|
687
|
+
let tmpValue = this.fable.Utility.getValueByHash(pValueObjectSet[i], pValueAddress, pManifest).toString();
|
|
688
|
+
let tmpAmount = this.parsePrecise(this.fable.Utility.getValueByHash(pValueObjectSet[i], pValueAmountAddress, pManifest), NaN);
|
|
689
|
+
|
|
690
|
+
if (!(tmpValue in tmpHistogram))
|
|
691
|
+
{
|
|
692
|
+
tmpHistogram[tmpValue] = 0;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (!isNaN(tmpAmount))
|
|
696
|
+
{
|
|
697
|
+
tmpHistogram[tmpValue] = this.addPrecise(tmpHistogram[tmpValue], tmpAmount);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return tmpHistogram;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
histogramAggregationByExactValueFromInternalState(pValueObjectSetAddress, pValueAddress, pValueAmountAddress)
|
|
705
|
+
{
|
|
706
|
+
if (!pValueObjectSetAddress)
|
|
707
|
+
{
|
|
708
|
+
return {};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let tmpValueObjectSet = this.fable.Utility.getInternalValueByHash(pValueObjectSetAddress);
|
|
712
|
+
return this.histogramAggregationByExactValue(tmpValueObjectSet, pValueAddress, pValueAmountAddress);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Given a value object set (an array of objects), find a specific entry when
|
|
717
|
+
* sorted by a specific value address. Supports -1 syntax for last entry.
|
|
718
|
+
* @param {Array} pValueObjectSet
|
|
719
|
+
* @param {string} pValueAddress
|
|
720
|
+
* @param {Object} pManifest
|
|
721
|
+
*/
|
|
722
|
+
entryInSet(pValueObjectSet, pValueAddress, pEntryIndex)
|
|
723
|
+
{
|
|
724
|
+
if (!Array.isArray(pValueObjectSet))
|
|
725
|
+
{
|
|
726
|
+
return pValueObjectSet;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (!pValueAddress)
|
|
730
|
+
{
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (isNaN(pEntryIndex) || pEntryIndex >= pValueObjectSet.length)
|
|
735
|
+
{
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
let tmpValueArray = pValueObjectSet.toSorted((pLeft, pRight) => { return this.comparePrecise(pLeft, pRight); });
|
|
740
|
+
let tmpIndex = (pEntryIndex === -1) ? tmpValueArray.length - 1 : pEntryIndex;
|
|
741
|
+
return tmpValueArray[tmpIndex];
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
smallestInSet(pValueObjectSet, pValueAddress)
|
|
745
|
+
{
|
|
746
|
+
return this.entryInSet(pValueObjectSet, pValueAddress, 0);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
largestInSet(pValueObjectSet, pValueAddress)
|
|
750
|
+
{
|
|
751
|
+
return this.entryInSet(pValueObjectSet, pValueAddress, -1);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Expects an array of objects, and an address in each object to sum. Expects
|
|
756
|
+
* an address to put the cumulative summation as well.
|
|
757
|
+
* @param {Array} pValueObjectSet
|
|
758
|
+
*/
|
|
759
|
+
cumulativeSummation(pValueObjectSet, pValueAddress, pCumulationResultAddress, pManifest)
|
|
760
|
+
{
|
|
761
|
+
if (!Array.isArray(pValueObjectSet))
|
|
762
|
+
{
|
|
763
|
+
return pValueObjectSet;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (!pValueAddress || !pCumulationResultAddress)
|
|
767
|
+
{
|
|
768
|
+
return pValueObjectSet;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
let tmpSummationValue = '0.0';
|
|
772
|
+
for (let i = 0; i < pValueObjectSet.length; i++)
|
|
773
|
+
{
|
|
774
|
+
let tmpValue = this.parsePrecise(this.fable.Utility.getValueByHash(pValueObjectSet[i], pValueAddress, pManifest));
|
|
775
|
+
|
|
776
|
+
if (isNaN(tmpValue))
|
|
777
|
+
{
|
|
778
|
+
this.fable.Utility.setValueByHash(pValueObjectSet[i], pCumulationResultAddress, tmpSummationValue, pManifest);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
tmpSummationValue = this.addPrecise(tmpValue, tmpSummationValue);
|
|
783
|
+
this.fable.Utility.setValueByHash(pValueObjectSet[i], pCumulationResultAddress, tmpSummationValue, pManifest);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return pValueObjectSet;
|
|
787
|
+
}
|
|
788
|
+
|
|
624
789
|
/**
|
|
625
790
|
* Finds the maximum value from a set of precise values.
|
|
626
791
|
*
|
|
@@ -128,6 +128,32 @@ class FableServiceUtility extends libFableServiceBase
|
|
|
128
128
|
return tmpManifest.getValueByHash(pObject, pValueAddress);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Set a value to an object by hash/address
|
|
133
|
+
* @param {object} pObject - The object to get the value from
|
|
134
|
+
* @param {string} pValueAddress - The manyfest hash/address of the value to get
|
|
135
|
+
* @param {object} pValue - The value to set
|
|
136
|
+
* @param {object} [pManifest] - The manyfest object to use; constructs one inline if not provided
|
|
137
|
+
* @returns {object} - The value from the object
|
|
138
|
+
*/
|
|
139
|
+
setValueByHash(pObject, pValueAddress, pValue, pManifest)
|
|
140
|
+
{
|
|
141
|
+
let tmpManifest = pManifest;
|
|
142
|
+
|
|
143
|
+
if (typeof(tmpManifest) == 'undefined')
|
|
144
|
+
{
|
|
145
|
+
// Lazily create a manifest if it doesn't exist
|
|
146
|
+
if (!this.manifest)
|
|
147
|
+
{
|
|
148
|
+
this.manifest = this.fable.newManyfest();
|
|
149
|
+
}
|
|
150
|
+
tmpManifest = this.manifest;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Get the value from the internal manifest and return it
|
|
154
|
+
return tmpManifest.setValueByHash(pObject, pValueAddress, pValue);
|
|
155
|
+
}
|
|
156
|
+
|
|
131
157
|
/**
|
|
132
158
|
* Get a value array from an object by hash/address list
|
|
133
159
|
* @param {object} pObject - The object to get the value from
|
|
@@ -177,6 +203,11 @@ class FableServiceUtility extends libFableServiceBase
|
|
|
177
203
|
|
|
178
204
|
createValueArrayByHashParametersFromInternal()
|
|
179
205
|
{
|
|
206
|
+
if (arguments.length < 2)
|
|
207
|
+
{
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
|
|
180
211
|
let tmpValueHashes = Array.prototype.slice.call(arguments);
|
|
181
212
|
return this.createValueArrayByHashes(this.fable, tmpValueHashes);
|
|
182
213
|
}
|
|
@@ -331,6 +331,40 @@ suite
|
|
|
331
331
|
Expect(parseFloat(tmpResult)).to.be.at.most(13.78);
|
|
332
332
|
|
|
333
333
|
|
|
334
|
+
return fDone();
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
test
|
|
338
|
+
(
|
|
339
|
+
'Complex Histogram Arithmatic',
|
|
340
|
+
(fDone)=>
|
|
341
|
+
{
|
|
342
|
+
let testFable = new libFable();
|
|
343
|
+
|
|
344
|
+
let testCityData = require('./data/cities.json');
|
|
345
|
+
testFable.AppData = { Cities: testCityData };
|
|
346
|
+
|
|
347
|
+
// let tmpDistribution = testFable.Math.histogramDistributionByExactValue(testFable.AppData.Cities, 'state');
|
|
348
|
+
|
|
349
|
+
// Expect(tmpDistribution.Alabama).to.equal(12);
|
|
350
|
+
// Expect(tmpDistribution.Colorado).to.equal(21);
|
|
351
|
+
// Expect(tmpDistribution.Florida).to.equal(73);
|
|
352
|
+
// Expect(tmpDistribution.Georgia).to.equal(18);
|
|
353
|
+
|
|
354
|
+
// Now through the solver
|
|
355
|
+
|
|
356
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
357
|
+
let tmpResultsObject = {};
|
|
358
|
+
let tmpDestinationObject = {};
|
|
359
|
+
|
|
360
|
+
_Parser.solve('DistributionResult = distributionhistogram("AppData.Cities", "state")', this.fable, tmpResultsObject, false, tmpDestinationObject);
|
|
361
|
+
_Parser.solve('AggregationResult = aggregationHistogram("AppData.Cities", "state", "population")', this.fable, tmpResultsObject, false, tmpDestinationObject);
|
|
362
|
+
|
|
363
|
+
Expect(tmpDestinationObject.DistributionResult.Alabama).to.equal(12);
|
|
364
|
+
Expect(tmpDestinationObject.DistributionResult.Colorado).to.equal(21);
|
|
365
|
+
|
|
366
|
+
Expect(tmpDestinationObject.AggregationResult.Alabama).to.equal('1279813');
|
|
367
|
+
|
|
334
368
|
return fDone();
|
|
335
369
|
}
|
|
336
370
|
);
|
|
@@ -116,6 +116,20 @@ suite
|
|
|
116
116
|
}
|
|
117
117
|
);
|
|
118
118
|
test
|
|
119
|
+
(
|
|
120
|
+
'Resolve a path.',
|
|
121
|
+
function(fTestComplete)
|
|
122
|
+
{
|
|
123
|
+
let testFable = new libFable();
|
|
124
|
+
let tmpFilePersistence = testFable.instantiateServiceProvider('FilePersistence');
|
|
125
|
+
|
|
126
|
+
Expect(tmpFilePersistence.resolvePath('tmp/tests/../othertests/names/'))
|
|
127
|
+
.to.equal(process.cwd()+'/tmp/othertests/names');
|
|
128
|
+
|
|
129
|
+
return fTestComplete();
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
test
|
|
119
133
|
(
|
|
120
134
|
'Create a recursive folder.',
|
|
121
135
|
function(fTestComplete)
|
package/test/Math_test.js
CHANGED
|
@@ -80,6 +80,37 @@ suite
|
|
|
80
80
|
}
|
|
81
81
|
);
|
|
82
82
|
test
|
|
83
|
+
(
|
|
84
|
+
'Cumulative Summation',
|
|
85
|
+
function(fDone)
|
|
86
|
+
{
|
|
87
|
+
let testFable = new libFable();
|
|
88
|
+
|
|
89
|
+
let tmpTestValueSet = (
|
|
90
|
+
[
|
|
91
|
+
{ Item: 'Lettuce', Quantity: 2, Price: "7.99" },
|
|
92
|
+
{ Item: 'Tomato', Quantity: 3, Price: "3.99" },
|
|
93
|
+
{ Item: 'Onion', Quantity: 1, Price: "1.99" },
|
|
94
|
+
{ Item: 'Cucumber', Quantity: 4, Price: "2.99" },
|
|
95
|
+
{ Item: 'Carrot', Quantity: 3, Price: "1.99" },
|
|
96
|
+
{ Item: 'Radish', Quantity: 2, Price: "1.49" },
|
|
97
|
+
{ Item: 'Celery', Quantity: 1, Price: "0.99" },
|
|
98
|
+
{ Item: 'Parsley', Quantity: 2, Price: "0.49" }
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
testFable.Math.cumulativeSummation(tmpTestValueSet, 'Price', 'RunningTotal');
|
|
102
|
+
|
|
103
|
+
Expect(tmpTestValueSet[0].RunningTotal).to.equal('7.99');
|
|
104
|
+
Expect(tmpTestValueSet[1].RunningTotal).to.equal('11.98');
|
|
105
|
+
Expect(tmpTestValueSet[2].RunningTotal).to.equal('13.97');
|
|
106
|
+
Expect(tmpTestValueSet[3].RunningTotal).to.equal('16.96');
|
|
107
|
+
Expect(tmpTestValueSet[4].RunningTotal).to.equal('18.95');
|
|
108
|
+
Expect(tmpTestValueSet[5].RunningTotal).to.equal('20.44');
|
|
109
|
+
|
|
110
|
+
return fDone();
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
test
|
|
83
114
|
(
|
|
84
115
|
'Parse Numbers',
|
|
85
116
|
function(fDone)
|
|
@@ -95,6 +126,35 @@ suite
|
|
|
95
126
|
}
|
|
96
127
|
);
|
|
97
128
|
|
|
129
|
+
test
|
|
130
|
+
(
|
|
131
|
+
'Histograms by Count',
|
|
132
|
+
function(fDone)
|
|
133
|
+
{
|
|
134
|
+
|
|
135
|
+
let testFable = new libFable();
|
|
136
|
+
|
|
137
|
+
let tmpTestValueSet = (
|
|
138
|
+
[
|
|
139
|
+
{ City: 'New York', State: 'NY', GDP: 1000000 },
|
|
140
|
+
{ City: 'Seattle', State: 'WA', GDP: 500000 },
|
|
141
|
+
{ City: 'Portland', State: 'OR', GDP: 250000 },
|
|
142
|
+
{ City: 'San Francisco', State: 'CA', GDP: 750000 },
|
|
143
|
+
{ City: 'Los Angeles', State: 'CA', GDP: 500000 },
|
|
144
|
+
{ City: 'San Diego', State: 'CA', GDP: 250000 },
|
|
145
|
+
{ City: 'Atlanta', State: 'GA', GDP: 100000 },
|
|
146
|
+
{ City: 'Savannah', State: 'GA', GDP: 50000 },
|
|
147
|
+
{ City: 'Athens', State: 'GA', GDP: 25000 }
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
let tmpHistogramByDistribution = testFable.Math.histogramDistributionByExactValue(tmpTestValueSet, 'State');
|
|
151
|
+
Expect(tmpHistogramByDistribution).to.deep.equal({ CA: 3, GA: 3, NY: 1, OR: 1, WA: 1 });
|
|
152
|
+
let tmpHistogramByAggregation = testFable.Math.histogramAggregationByExactValue(tmpTestValueSet, 'State', 'GDP');
|
|
153
|
+
Expect(tmpHistogramByAggregation).to.deep.equal({ CA: "1500000", GA: "175000", NY: "1000000", OR: "250000", WA: "500000" });
|
|
154
|
+
return fDone();
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
98
158
|
test
|
|
99
159
|
(
|
|
100
160
|
'Round Numbers',
|