coerce-exemplar.test.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. var util = require('util');
  2. var assert = require('assert');
  3. var _ = require('@sailshq/lodash');
  4. var rttc = require('../');
  5. describe('.coerceExemplar()', function() {
  6. // From the docs: (just for reference)
  7. // =================================================
  8. //
  9. // + Empty dictionaries become generic dictionaries (`{}`). The most specific exemplar which can accept an empty dictionary is the generic dictionary.
  10. // + Empty arrays become generic arrays (`[]`). Since we don't know the contents, we have to assume this array could be heterogeneous (i.e. have items with different types).
  11. // + Multi-item arrays become pattern arrays, and any extra items (other than the first one) are lopped off.
  12. // + Functions become '->'.
  13. // + `null` becomes '*'.
  14. // + If the top-level value is `undefined`, it becomes '==='.
  15. // + '->' becomes 'an arrow symbol'.
  16. // + '*' becomes 'a star symbol'.
  17. // + '===' becomes '3 equal signs'.
  18. // + `NaN`, `Infinity`, and `-Infinity` become '===' (or 0 if `useStrict` is disabled)
  19. // + Nested items and keys with `undefined` values are stripped.
  20. // + Other than the exceptions mentioned above, non-JSON-serializable things (like circular references) are boiled away when this calls `dehydrate` internally.
  21. it('should replace "json", "ref", and "lamda" exemplars with strings', function (){
  22. coerceExemplarAndVerifyDeep('*', 'a star symbol');
  23. coerceExemplarAndVerifyDeep('->', 'an arrow symbol');
  24. coerceExemplarAndVerifyDeep('<=', 'an arrow symbol');
  25. coerceExemplarAndVerifyDeep('<---', 'an arrow symbol');
  26. coerceExemplarAndVerifyDeep('===', '3 equal signs');
  27. });
  28. it('should NOT replace "json", "ref", and "lamda" exemplars with strings if the `allowSpecialSyntax` flag is enabled', function (){
  29. coerceExemplarAndVerifyDeep('*', '*', true);
  30. coerceExemplarAndVerifyDeep('->', '->', true);
  31. coerceExemplarAndVerifyDeep('<=', '<=', true);
  32. coerceExemplarAndVerifyDeep('<---', '<---', true);
  33. coerceExemplarAndVerifyDeep('===', '===', true);
  34. });
  35. it('should leave empty dictionaries, empty arrays, strings, numbers, and booleans alone (and recursively process things)', function (){
  36. coerceExemplarAndVerifyDeep({}, {});
  37. coerceExemplarAndVerifyDeep([], []);
  38. coerceExemplarAndVerifyDeep(true, true);
  39. coerceExemplarAndVerifyDeep(false, false);
  40. coerceExemplarAndVerifyDeep(1, 1);
  41. coerceExemplarAndVerifyDeep(-1.8, -1.8);
  42. coerceExemplarAndVerifyDeep(0, 0);
  43. });
  44. it('should coerce all `null` into "*"', function() {
  45. coerceExemplarAndVerifyDeep(null, '*');
  46. });
  47. it('should coerce circular references (i.e. works just like rttc.dehydrate(), but allowing functions and nulls)', function() {
  48. var hallOfMirrors = { giveUp: function (){ throw new Error('psht.');} };
  49. hallOfMirrors.left = { down: null, up: null };
  50. hallOfMirrors.right = { down: null, up: null };
  51. hallOfMirrors.left.right = hallOfMirrors.right;
  52. hallOfMirrors.right.right = hallOfMirrors.right;
  53. hallOfMirrors.left.right.left = hallOfMirrors.left;
  54. assert.deepEqual(rttc.coerceExemplar(hallOfMirrors), {
  55. giveUp: '->',
  56. left: { down: '*', up: '*', right: {
  57. down: '*', up: '*', right: '[Circular ~.left.right]', left: '[Circular ~.left]'
  58. } },
  59. right: { down: '*', up: '*', right: '[Circular ~.right]', left: {
  60. down: '*', up: '*', right: '[Circular ~.right]'
  61. } }
  62. });
  63. });
  64. describe('by default (when `treatTopLvlUndefinedAsRef` is enabled)', function (){
  65. it('should return "===" if `undefined` is specified at the top-level', function (){
  66. assert.equal(rttc.coerceExemplar(undefined), '===');
  67. });
  68. });
  69. describe('when `treatTopLvlUndefinedAsRef` is explicitly disabled', function (){
  70. it('should return "*" if `undefined` is specified at the top-level (because it acts like it was `null`)', function (){
  71. assert.equal(rttc.coerceExemplar(undefined, false, false), '*');
  72. });
  73. });
  74. it('should squish `NaN`, `Infinity`, and `-Infinity` to `===`', function (){
  75. coerceExemplarAndVerifyDeep(NaN, '===');
  76. coerceExemplarAndVerifyDeep(Infinity, '===');
  77. coerceExemplarAndVerifyDeep(-Infinity, '===');
  78. });
  79. it('should coerce Dates, RegExps, and Errors into comparable string exemplars', function (){
  80. coerceExemplarAndVerifyDeep(new Date('November 5, 1605 GMT'), '1605-11-05T00:00:00.000Z');
  81. coerceExemplarAndVerifyDeep(/some regexp/, '/some regexp/');
  82. assert(_.isString(rttc.coerceExemplar(new Error('asdf'))));
  83. assert(_.isString(
  84. rttc.coerceExemplar({x: new Error('asdf')}).x
  85. ));
  86. assert(_.isString(
  87. rttc.coerceExemplar([new Error('asdf')])[0]
  88. ));
  89. });
  90. it('should coerce Streams and Buffers into "*" (which is sort of weird, but it\'s ok)', function (){
  91. coerceExemplarAndVerifyDeep( new Buffer('asdf'), '*' );
  92. coerceExemplarAndVerifyDeep( new (require('stream').Readable)(), '*' );
  93. });
  94. it('should coerce functions into "->"', function (){
  95. coerceExemplarAndVerifyDeep({
  96. salutation: 'Mr.',
  97. hobbies: ['knitting'],
  98. knit: function toKnit(yarn, needle, talent, patience) {
  99. while (patience) {
  100. patience -= talent(needle, yarn);
  101. }
  102. },
  103. medicalInfo: {
  104. numYearsBlueberryAbuse: 12.5,
  105. latestBloodWork: {}
  106. }
  107. }, {
  108. salutation: 'Mr.',
  109. hobbies: ['knitting'],
  110. knit: '->',
  111. medicalInfo: {
  112. numYearsBlueberryAbuse: 12.5,
  113. latestBloodWork: {}
  114. }
  115. });
  116. });
  117. it('should strip nested keys and items w/ undefined values (but keep `null`)', function() {
  118. coerceExemplarAndVerifyDeep({numDandelions: 1, numAmaranth: 2, numLambsQuarters: undefined, numThistle: 4}, {numDandelions: 1, numAmaranth: 2, numThistle: 4});
  119. coerceExemplarAndVerifyDeep({numDandelions: [undefined], numAmaranth: 2, numLambsQuarters: undefined, numThistle: 4}, {numDandelions: [], numAmaranth: 2, numThistle: 4});
  120. coerceExemplarAndVerifyDeep({numDandelions: [null], numAmaranth: 2, numLambsQuarters: null, numThistle: 4}, {numDandelions: ['*'], numAmaranth: 2, numLambsQuarters: '*', numThistle: 4});
  121. });
  122. describe('given a multi-item array', function (){
  123. it('should union together the items of arrays into a single pattern exemplar', function (){
  124. coerceExemplarAndVerifyDeep([1], [1]);
  125. coerceExemplarAndVerifyDeep([1,4], [4]);
  126. coerceExemplarAndVerifyDeep(['1','4'], ['4']);
  127. coerceExemplarAndVerifyDeep([1,'1'], ['*']);
  128. coerceExemplarAndVerifyDeep([0,false], ['*']);
  129. coerceExemplarAndVerifyDeep([false, 7, 4, true, -4, 0, 89], ['*']);
  130. coerceExemplarAndVerifyDeep([{x:false}, [7], {y: {z: 4}}, [[]], 'whatever', true], ['*']);
  131. // TODO: come back to this-- seems like it really ought to be ["*"]
  132. coerceExemplarAndVerifyDeep([
  133. [{ x: false }],
  134. [7],
  135. [{ y: { z: 4 } }],
  136. [ [] ],
  137. ['whatever'],
  138. [true]
  139. ], [
  140. ['===']
  141. ]);
  142. });
  143. it('when `useStrict` flag is explicitly disabled', function (){
  144. coerceExemplarAndVerifyDeep([1], [1], false, false, false);
  145. coerceExemplarAndVerifyDeep([1,4], [4], false, false, false);
  146. coerceExemplarAndVerifyDeep(['1','4'], ['4'], false, false, false);
  147. coerceExemplarAndVerifyDeep([1,'1'], ['1'], false, false, false);
  148. coerceExemplarAndVerifyDeep([0,false], [0], false, false, false);
  149. coerceExemplarAndVerifyDeep([false, 7, 4, true, -4, 0, 89], [89], false, false, false);
  150. coerceExemplarAndVerifyDeep([false, {}, 4, true, -4, 0, 89], ['*'], false, false, false);
  151. coerceExemplarAndVerifyDeep([{x:false}, [7], {y: {z: 4}}, [[]], 'whatever', true], ['*'], false, false, false);
  152. coerceExemplarAndVerifyDeep(NaN, 0, false, false, false);
  153. coerceExemplarAndVerifyDeep(Infinity, 0, false, false, false);
  154. coerceExemplarAndVerifyDeep(-Infinity, 0, false, false, false);
  155. // TODO: come back to this-- seems like it really ought to be ["*"]
  156. coerceExemplarAndVerifyDeep([
  157. [{ x: false }],
  158. [7],
  159. [{ y: { z: 4 } }],
  160. [ [] ],
  161. ['whatever'],
  162. [true]
  163. ], [
  164. ['===']
  165. ], false, false, false);
  166. });
  167. });
  168. });
  169. /**
  170. * Call `coerceExemplar` using the provided value and test that
  171. * the expected result comes back, even when wrapping the value in
  172. * all sorts of dictionaries and arrays and such.
  173. *
  174. * @param {*} value
  175. * @param {*} expected
  176. * @param {Boolean} allowSpecialSyntax
  177. * @param {Boolean} treatTopLvlUndefinedAsRef
  178. * @param {Boolean} useStrict
  179. */
  180. function coerceExemplarAndVerifyDeep(value, expected, allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict){
  181. assert.deepEqual(rttc.coerceExemplar(value, allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict), expected);
  182. assert.deepEqual(rttc.coerceExemplar({x:value}, allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict), {x:expected});
  183. assert.deepEqual(rttc.coerceExemplar({x: [value]}, allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict), {x:[expected]});
  184. assert.deepEqual(rttc.coerceExemplar([{x: value}], allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict), [{x:expected}]);
  185. assert.deepEqual(rttc.coerceExemplar([value], allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict), [expected]);
  186. assert.deepEqual(rttc.coerceExemplar([[value]], allowSpecialSyntax, treatTopLvlUndefinedAsRef, useStrict), [[expected]]);
  187. }