manyfest 1.0.13 → 1.0.15

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.
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @license MIT
3
+ * @author <steven@velozo.com>
4
+ */
5
+ let libSimpleLog = require('./Manyfest-LogToConsole.js');
6
+ let libPrecedent = require('precedent');
7
+ let fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js');
8
+
9
+ /**
10
+ * Object Address Resolver - SetValue
11
+ *
12
+ * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
13
+ * be extremely clear what is going on in the recursion for
14
+ * each of the three address resolution functions.
15
+ *
16
+ * Although there is some opportunity to repeat ourselves a
17
+ * bit less in this codebase (e.g. with detection of arrays
18
+ * versus objects versus direct properties), it can make
19
+ * debugging.. challenging. The minified version of the code
20
+ * optimizes out almost anything repeated in here. So please
21
+ * be kind and rewind... meaning please keep the codebase less
22
+ * terse and more verbose so humans can comprehend it.
23
+ *
24
+ *
25
+ * @class ManyfestObjectAddressSetValue
26
+ */
27
+ class ManyfestObjectAddressSetValue
28
+ {
29
+ constructor(pInfoLog, pErrorLog)
30
+ {
31
+ // Wire in logging
32
+ this.logInfo = (typeof(pInfoLog) == 'function') ? pInfoLog : libSimpleLog;
33
+ this.logError = (typeof(pErrorLog) == 'function') ? pErrorLog : libSimpleLog;
34
+
35
+ this.elucidatorSolver = false;
36
+ this.elucidatorSolverState = {};
37
+
38
+ this.cleanWrapCharacters = fCleanWrapCharacters;
39
+ }
40
+
41
+ // Set the value of an element at an address
42
+ setValueAtAddress (pObject, pAddress, pValue)
43
+ {
44
+ // Make sure pObject is an object
45
+ if (typeof(pObject) != 'object') return false;
46
+ // Make sure pAddress is a string
47
+ if (typeof(pAddress) != 'string') return false;
48
+
49
+ let tmpSeparatorIndex = pAddress.indexOf('.');
50
+
51
+ if (tmpSeparatorIndex == -1)
52
+ {
53
+ // Check if it's a boxed property
54
+ let tmpBracketStartIndex = pAddress.indexOf('[');
55
+ let tmpBracketStopIndex = pAddress.indexOf(']');
56
+ // Boxed elements look like this:
57
+ // MyValues[10]
58
+ // MyValues['Name']
59
+ // MyValues["Age"]
60
+ // MyValues[`Cost`]
61
+ //
62
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
63
+ // The requirements to detect a boxed element are:
64
+ // 1) The start bracket is after character 0
65
+ if ((tmpBracketStartIndex > 0)
66
+ // 2) The end bracket has something between them
67
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
68
+ // 3) There is data
69
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 1))
70
+ {
71
+ // The "Name" of the Object contained too the left of the bracket
72
+ let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
73
+
74
+ // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
75
+ // This is a rare case where Arrays testing as Objects is useful
76
+ if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
77
+ {
78
+ return false;
79
+ }
80
+
81
+ // The "Reference" to the property within it, either an array element or object property
82
+ let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
83
+ // Attempt to parse the reference as a number, which will be used as an array element
84
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
85
+
86
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
87
+ // This seems confusing to me at first read, so explaination:
88
+ // Is the Boxed Object an Array? TRUE
89
+ // And is the Reference inside the boxed Object not a number? TRUE
90
+ // --> So when these are in agreement, it's an impossible access state
91
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
92
+ {
93
+ return false;
94
+ }
95
+
96
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
97
+ // otherwise we will try to treat it as a dynamic object property.
98
+ if (isNaN(tmpBoxedPropertyNumber))
99
+ {
100
+ // This isn't a number ... let's treat it as a dynamic object property.
101
+ // We would expect the property to be wrapped in some kind of quotes so strip them
102
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
103
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
104
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
105
+
106
+ // Return the value in the property
107
+ pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = pValue;
108
+ return true;
109
+ }
110
+ else
111
+ {
112
+ pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber] = pValue;
113
+ return true;
114
+ }
115
+ }
116
+ else
117
+ {
118
+ // Now is the time in recursion to set the value in the object
119
+ pObject[pAddress] = pValue;
120
+ return true;
121
+ }
122
+ }
123
+ else
124
+ {
125
+ let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
126
+ let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
127
+
128
+ // Test if the tmpNewAddress is an array or object
129
+ // Check if it's a boxed property
130
+ let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
131
+ let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
132
+ // Boxed elements look like this:
133
+ // MyValues[42]
134
+ // MyValues['Color']
135
+ // MyValues["Weight"]
136
+ // MyValues[`Diameter`]
137
+ //
138
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
139
+ // The requirements to detect a boxed element are:
140
+ // 1) The start bracket is after character 0
141
+ if ((tmpBracketStartIndex > 0)
142
+ // 2) The end bracket has something between them
143
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
144
+ // 3) There is data
145
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 1))
146
+ {
147
+ let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
148
+
149
+ let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
150
+
151
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
152
+
153
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
154
+ // This seems confusing to me at first read, so explaination:
155
+ // Is the Boxed Object an Array? TRUE
156
+ // And is the Reference inside the boxed Object not a number? TRUE
157
+ // --> So when these are in agreement, it's an impossible access state
158
+ // This could be a failure in the recursion chain because they passed something like this in:
159
+ // StudentData.Sections.Algebra.Students[1].Tardy
160
+ // BUT
161
+ // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
162
+ // This could be a failure in the recursion chain because they passed something like this in:
163
+ // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
164
+ // BUT
165
+ // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
166
+ // TODO: Should this be an error or something? Should we keep a log of failures like this?
167
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
168
+ {
169
+ return false;
170
+ }
171
+
172
+ //This is a bracketed value
173
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
174
+ // otherwise we will try to reat it as a dynamic object property.
175
+ if (isNaN(tmpBoxedPropertyNumber))
176
+ {
177
+ // This isn't a number ... let's treat it as a dynanmic object property.
178
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
179
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
180
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
181
+
182
+ // Recurse directly into the subobject
183
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, pValue);
184
+ }
185
+ else
186
+ {
187
+ // We parsed a valid number out of the boxed property name, so recurse into the array
188
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, pValue);
189
+ }
190
+ }
191
+
192
+ // If there is an object property already named for the sub object, but it isn't an object
193
+ // then the system can't set the value in there. Error and abort!
194
+ if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
195
+ {
196
+ if (!pObject.hasOwnProperty('__ERROR'))
197
+ pObject['__ERROR'] = {};
198
+ // Put it in an error object so data isn't lost
199
+ pObject['__ERROR'][pAddress] = pValue;
200
+ return false;
201
+ }
202
+ else if (pObject.hasOwnProperty(tmpSubObjectName))
203
+ {
204
+ // If there is already a subobject pass that to the recursive thingy
205
+ return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
206
+ }
207
+ else
208
+ {
209
+ // Create a subobject and then pass that
210
+ pObject[tmpSubObjectName] = {};
211
+ return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
212
+ }
213
+ }
214
+ }
215
+ };
216
+
217
+ module.exports = ManyfestObjectAddressSetValue;
@@ -7,14 +7,14 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
7
7
  /**
8
8
  * Object Address Generation
9
9
  *
10
- * Automagically generate addresses and properties based on a passed-in object,
10
+ * Automagically generate addresses and properties based on a passed-in object,
11
11
  * to be used for easy creation of schemas. Meant to simplify the lives of
12
12
  * developers wanting to create schemas without typing a bunch of stuff.
13
- *
13
+ *
14
14
  * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
15
15
  * be extremely clear what is going on in the recursion for
16
16
  * each of the three address resolution functions.
17
- *
17
+ *
18
18
  * Although there is some opportunity to repeat ourselves a
19
19
  * bit less in this codebase (e.g. with detection of arrays
20
20
  * versus objects versus direct properties), it can make
@@ -22,7 +22,7 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
22
22
  * optimizes out almost anything repeated in here. So please
23
23
  * be kind and rewind... meaning please keep the codebase less
24
24
  * terse and more verbose so humans can comprehend it.
25
- *
25
+ *
26
26
  *
27
27
  * @class ManyfestObjectAddressGeneration
28
28
  */
@@ -42,7 +42,7 @@ class ManyfestObjectAddressGeneration
42
42
  // quickly. This is not meant to be used directly to generate schemas, but
43
43
  // instead as a starting point for scripts or UIs.
44
44
  //
45
- // This will return a mega set of key:value pairs with all possible schema
45
+ // This will return a mega set of key:value pairs with all possible schema
46
46
  // permutations and default values (when not an object) and everything else.
47
47
  generateAddressses (pObject, pBaseAddress, pSchema)
48
48
  {
@@ -93,7 +93,7 @@ class ManyfestObjectAddressGeneration
93
93
  {
94
94
  tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
95
95
  }
96
-
96
+
97
97
  for (let i = 0; i < pObject.length; i++)
98
98
  {
99
99
  this.generateAddressses(pObject[i], `${tmpBaseAddress}[${i}]`, tmpSchema);
@@ -107,13 +107,13 @@ class ManyfestObjectAddressGeneration
107
107
  tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
108
108
  tmpBaseAddress += '.';
109
109
  }
110
-
110
+
111
111
  let tmpObjectProperties = Object.keys(pObject);
112
112
 
113
113
  for (let i = 0; i < tmpObjectProperties.length; i++)
114
114
  {
115
115
  this.generateAddressses(pObject[tmpObjectProperties[i]], `${tmpBaseAddress}${tmpObjectProperties[i]}`, tmpSchema);
116
- }
116
+ }
117
117
  }
118
118
  break;
119
119
  case 'symbol':
@@ -30,7 +30,7 @@ class ManyfestSchemaManipulation
30
30
  // And then an address mapping (basically a Hash->Address map)
31
31
  // {
32
32
  // "a": "New.Address.Of.a",
33
- // "b": "New.Address.Of.b"
33
+ // "b": "New.Address.Of.b"
34
34
  // }
35
35
  //
36
36
  // NOTE: This mutates the schema object permanently, altering the base hash.
@@ -123,14 +123,14 @@ class ManyfestSchemaManipulation
123
123
  let tmpDescriptorAddresses = Object.keys(tmpSource);
124
124
 
125
125
  tmpDescriptorAddresses.forEach(
126
- (pDescriptorAddress) =>
126
+ (pDescriptorAddress) =>
127
127
  {
128
128
  if (!tmpNewManyfestSchemaDescriptors.hasOwnProperty(pDescriptorAddress))
129
129
  {
130
130
  tmpNewManyfestSchemaDescriptors[pDescriptorAddress] = tmpSource[pDescriptorAddress];
131
131
  }
132
132
  });
133
-
133
+
134
134
  return tmpNewManyfestSchemaDescriptors;
135
135
  }
136
136
  }
@@ -7,7 +7,10 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
7
7
  let libPrecedent = require('precedent');
8
8
 
9
9
  let libHashTranslation = require('./Manyfest-HashTranslation.js');
10
- let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js');
10
+ let libObjectAddressCheckAddressExists = require('./Manyfest-ObjectAddress-CheckAddressExists.js');
11
+ let libObjectAddressGetValue = require('./Manyfest-ObjectAddress-GetValue.js');
12
+ let libObjectAddressSetValue = require('./Manyfest-ObjectAddress-SetValue.js');
13
+ let libObjectAddressDeleteValue = require('./Manyfest-ObjectAddress-DeleteValue.js');
11
14
  let libObjectAddressGeneration = require('./Manyfest-ObjectAddressGeneration.js');
12
15
  let libSchemaManipulation = require('./Manyfest-SchemaManipulation.js');
13
16
 
@@ -26,12 +29,15 @@ class Manyfest
26
29
  this.logError = (typeof(pErrorLog) === 'function') ? pErrorLog : libSimpleLog;
27
30
 
28
31
  // Create an object address resolver and map in the functions
29
- this.objectAddressResolver = new libObjectAddressResolver(this.logInfo, this.logError);
32
+ this.objectAddressCheckAddressExists = new libObjectAddressCheckAddressExists(this.logInfo, this.logError);
33
+ this.objectAddressGetValue = new libObjectAddressGetValue(this.logInfo, this.logError);
34
+ this.objectAddressSetValue = new libObjectAddressSetValue(this.logInfo, this.logError);
35
+ this.objectAddressDeleteValue = new libObjectAddressDeleteValue(this.logInfo, this.logError);
30
36
 
31
37
  this.options = (
32
38
  {
33
39
  strict: false,
34
- defaultValues:
40
+ defaultValues:
35
41
  {
36
42
  "String": "",
37
43
  "Number": 0,
@@ -83,7 +89,19 @@ class Manyfest
83
89
  this.dataSolverState = {};
84
90
 
85
91
  this.libElucidator = undefined;
86
- this.objectAddressResolver.elucidatorSolver = false;
92
+ }
93
+
94
+ setElucidatorSolvers(pElucidatorSolver, pElucidatorSolverState)
95
+ {
96
+ this.objectAddressCheckAddressExists.elucidatorSolver = pElucidatorSolver;
97
+ this.objectAddressGetValue.elucidatorSolver = pElucidatorSolver;
98
+ this.objectAddressSetValue.elucidatorSolver = pElucidatorSolver;
99
+ this.objectAddressDeleteValue.elucidatorSolver = pElucidatorSolver;
100
+
101
+ this.objectAddressCheckAddressExists.elucidatorSolverState = pElucidatorSolverState;
102
+ this.objectAddressGetValue.elucidatorSolverState = pElucidatorSolverState;
103
+ this.objectAddressSetValue.elucidatorSolverState = pElucidatorSolverState;
104
+ this.objectAddressDeleteValue.elucidatorSolverState = pElucidatorSolverState;
87
105
  }
88
106
 
89
107
  clone()
@@ -168,7 +186,6 @@ class Manyfest
168
186
  // This is mostly meant for if statements to filter.
169
187
  // Basically on aggregation, if a filter is set it will set "keep record" to true and let the solver decide differently.
170
188
  this.dataSolvers = new libElucidator(pManifest.Solvers, this.logInfo, this.logError);
171
- this.objectAddressResolver.elucidatorSolver = this.dataSolvers;
172
189
 
173
190
  // Load the solver state in so each instruction can have internal config
174
191
  // TODO: Should this just be a part of the lower layer pattern?
@@ -177,7 +194,8 @@ class Manyfest
177
194
  {
178
195
  this.dataSolverState[tmpSolverKeys] = pManifest.Solvers[tmpSolverKeys[i]];
179
196
  }
180
- this.objectAddressResolver.elucidatorSolverState = this.dataSolverState;
197
+
198
+ this.setElucidatorSolvers(this.dataSolvers, this.dataSolverState);
181
199
  }
182
200
  }
183
201
 
@@ -236,7 +254,7 @@ class Manyfest
236
254
  {
237
255
  this.logError(`(${this.scope}) Error loading object descriptor for address '${pAddress}' from manifest object. Expecting an object but property was type ${typeof(pDescriptor)}.`);
238
256
  return false;
239
- }
257
+ }
240
258
  }
241
259
 
242
260
  getDescriptorByHash(pHash)
@@ -272,7 +290,7 @@ class Manyfest
272
290
  // Check if an element exists at an address
273
291
  checkAddressExists (pObject, pAddress)
274
292
  {
275
- return this.objectAddressResolver.checkAddressExists(pObject, pAddress);
293
+ return this.objectAddressCheckAddressExists.checkAddressExists(pObject, pAddress);
276
294
  }
277
295
 
278
296
  // Turn a hash into an address, factoring in the translation table.
@@ -293,7 +311,7 @@ class Manyfest
293
311
  {
294
312
  tmpAddress = this.elementHashes[this.hashTranslations.translate(pHash)];
295
313
  }
296
- // Use the level of indirection only in the Translation Table
314
+ // Use the level of indirection only in the Translation Table
297
315
  else if (tmpInTranslationTable)
298
316
  {
299
317
  tmpAddress = this.hashTranslations.translate(pHash);
@@ -325,7 +343,7 @@ class Manyfest
325
343
  // Get the value of an element at an address
326
344
  getValueAtAddress (pObject, pAddress)
327
345
  {
328
- let tmpValue = this.objectAddressResolver.getValueAtAddress(pObject, pAddress);
346
+ let tmpValue = this.objectAddressGetValue.getValueAtAddress(pObject, pAddress);
329
347
 
330
348
  if (typeof(tmpValue) == 'undefined')
331
349
  {
@@ -342,11 +360,22 @@ class Manyfest
342
360
  return this.setValueAtAddress(pObject, this.resolveHashAddress(pHash), pValue);
343
361
  }
344
362
 
345
-
346
363
  // Set the value of an element at an address
347
364
  setValueAtAddress (pObject, pAddress, pValue)
348
365
  {
349
- return this.objectAddressResolver.setValueAtAddress(pObject, pAddress, pValue);
366
+ return this.objectAddressSetValue.setValueAtAddress(pObject, pAddress, pValue);
367
+ }
368
+
369
+ // Delete the value of an element by its hash
370
+ deleteValueByHash(pObject, pHash, pValue)
371
+ {
372
+ return this.deleteValueAtAddress(pObject, this.resolveHashAddress(pHash), pValue);
373
+ }
374
+
375
+ // Delete the value of an element at an address
376
+ deleteValueAtAddress (pObject, pAddress, pValue)
377
+ {
378
+ return this.objectAddressDeleteValue.deleteValueAtAddress(pObject, pAddress, pValue);
350
379
  }
351
380
 
352
381
  // Validate the consistency of an object against the schema
@@ -438,7 +467,7 @@ class Manyfest
438
467
  {
439
468
  addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is not parsable as a Date by Javascript`);
440
469
  }
441
-
470
+
442
471
  default:
443
472
  // Check if this is a string, in the default case
444
473
  // Note this is only when a DataType is specified and it is an unrecognized data type.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Unit tests for Manyfest
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ var Chai = require("chai");
10
+ var Expect = Chai.expect;
11
+
12
+ let libManyfest = require('../source/Manyfest.js');
13
+
14
+ let _SampleDataArchiveOrgFrankenberry = JSON.parse(JSON.stringify(require('./Data-Archive-org-Frankenberry.json')));
15
+
16
+ suite
17
+ (
18
+ 'Manyfest Object Property Delete',
19
+ function()
20
+ {
21
+ setup (()=> {} );
22
+
23
+ suite
24
+ (
25
+ 'Basic Delete',
26
+ ()=>
27
+ {
28
+ test
29
+ (
30
+ 'It should be trivial to delete subproperties without a schema.',
31
+ (fTestComplete)=>
32
+ {
33
+ let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
34
+ // Property not in schema:
35
+ Expect(_Manyfest.deleteValueAtAddress(_SampleDataArchiveOrgFrankenberry, 'metadata.title'))
36
+ .to.equal(true);
37
+ Expect(_SampleDataArchiveOrgFrankenberry.metadata.title)
38
+ .to.equal(undefined);
39
+ fTestComplete();
40
+ }
41
+ );
42
+ test
43
+ (
44
+ 'It should be trivial to delete subproperties with a schema by hash.',
45
+ (fTestComplete)=>
46
+ {
47
+ let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
48
+ // Property not schema, accessed by hash:
49
+ let tmpDeleted = _Manyfest.deleteValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
50
+ Expect(_SampleDataArchiveOrgFrankenberry.metadata.creator)
51
+ .to.equal(undefined);
52
+ Expect(tmpDeleted)
53
+ .to.equal(true);
54
+ fTestComplete();
55
+ }
56
+ );
57
+ test
58
+ (
59
+ 'Nothing bad should happen deleting a non existant property.',
60
+ (fTestComplete)=>
61
+ {
62
+ let animalManyfest = new libManyfest(
63
+ {
64
+ "Scope": "Animal",
65
+ "Descriptors":
66
+ {
67
+ "IDAnimal": { "Name":"Database ID", "Description":"The unique integer-based database identifier for an Animal record.", "DataType":"Integer" },
68
+ "Name": { "Description":"The animal's colloquial species name (e.g. Rabbit, Dog, Bear, Mongoose)." },
69
+ "Type": { "Description":"Whether or not the animal is wild, domesticated, agricultural, in a research lab or a part of a zoo.." },
70
+ "MedicalStats":
71
+ {
72
+ "Name":"Medical Statistics", "Description":"Basic medical statistics for this animal"
73
+ },
74
+ "MedicalStats.Temps.MinET": { "Name":"Minimum Environmental Temperature", "NameShort":"MinET", "Description":"Safest minimum temperature for this animal to survive in."},
75
+ "MedicalStats.Temps.MaxET": { "Name":"Maximum Environmental Temperature", "NameShort":"MaxET", "Description":"Safest maximum temperature for this animal to survive in."},
76
+ "MedicalStats.Temps.CET":
77
+ {
78
+ "Name":"Comfortable Environmental Temperature",
79
+ "NameShort":"Comf Env Temp",
80
+ "Hash":"ComfET",
81
+ "Description":"The most comfortable temperature for this animal to survive in.",
82
+ "Default": "96.8"
83
+ }
84
+ }
85
+ });
86
+
87
+ Expect(animalManyfest.deleteValueAtAddress({MedicalStats: { Temps: { MinET:200 }},Name:'Froggy'}, 'MedicalStats.Temps.HighETBUTIDONTEXISTDOI'))
88
+ .to.equal(true);
89
+
90
+ fTestComplete();
91
+ }
92
+ );
93
+ }
94
+ );
95
+ }
96
+ );