intersection.test.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /**
  2. * Module dependencies
  3. */
  4. var util = require('util');
  5. var assert = require('assert');
  6. var _ = require('@sailshq/lodash');
  7. var rttc = require('../');
  8. describe('.intersection()', function() {
  9. // ┌─┐┬─┐ ┬┌┬┐┬ ┬┬─┐┌─┐┌─┐
  10. // ├┤ │┌┴┬┘ │ │ │├┬┘├┤ └─┐
  11. // └ ┴┴ └─ ┴ └─┘┴└─└─┘└─┘
  12. // Fixtures
  13. var LYDIA = {
  14. id: 102432,
  15. name: 'Lydia Villa-Komaroff',
  16. age: 68,
  17. meta: '===',
  18. coordinates: { x: 10, y: -4 },
  19. friends: [
  20. {
  21. id: 93825,
  22. name: 'José Orozco',
  23. age: 65,
  24. meta: '===',
  25. coordinates: { x: -391, y: 47.3295 }
  26. }
  27. ]
  28. };
  29. // Shortcut used below for convenience:
  30. var given = function (exemplar0, exemplar1){
  31. return {
  32. expect: function (expectedResult){
  33. // Perform the standard, usual call to `rttc.intersection()`
  34. // (using exemplars rather than type schemas and with `strict` set to `false`)
  35. var actualResult = rttc.intersection(exemplar0, exemplar1, true, false);
  36. assert.deepEqual(expectedResult, actualResult);
  37. }
  38. };
  39. };
  40. // ┌┬┐┌─┐┌─┐┌┬┐┌─┐
  41. // │ ├┤ └─┐ │ └─┐
  42. // ┴ └─┘└─┘ ┴ └─┘
  43. describe('using exemplars & loose validation rules', function() {
  44. // Types always intersect with themselves, with an identity result.
  45. describe('when intersected with the same type', function (){
  46. describe('should result in an exemplar with the same type schema', function (){
  47. // "string" ∩ "string" <====> "string"
  48. it('"string" ∩ "string"', function (){
  49. given('foo', 'bar').expect('bar');
  50. given('bar', 'foo').expect('foo');
  51. });
  52. // "number" ∩ "number" <====> "number"
  53. it('"number" ∩ "number"', function (){
  54. given(-9, 32).expect(32);
  55. given(32, -9).expect(-9);
  56. });
  57. // "boolean" ∩ "boolean" <====> "boolean"
  58. it('"boolean" ∩ "boolean"', function (){
  59. given(true, true).expect(true);
  60. given(false, false).expect(false);
  61. given(false, true).expect(true);
  62. given(true, false).expect(false);
  63. });
  64. // "lamda" ∩ "lamda" <====> "lamda"
  65. it('"lamda" ∩ "lamda"', function (){
  66. given('->', '->').expect('->');
  67. });
  68. // Generic dictionary exemplars
  69. it('Generic dictionary exemplars', function (){
  70. given({}, {}).expect({});
  71. });
  72. // "json" ∩ "json" <====> "json"
  73. it('"json" ∩ "json"', function (){
  74. given('*', '*').expect('*');
  75. });
  76. // "ref" ∩ "ref" <====> "ref"
  77. it('"ref" ∩ "ref"', function (){
  78. given('===', '===').expect('===');
  79. });
  80. // "Generic array" exemplars (for compatibility)
  81. // [] ∩ []
  82. // -AKA- <====> ["*"]
  83. // ["*"] ∩ ["*"]
  84. it('"Generic array" exemplars (for compatibility)', function (){
  85. given([], []).expect([]);
  86. given([], ['*']).expect(['*']);
  87. given(['*'], []).expect(['*']);
  88. given(['*'], ['*']).expect(['*']);
  89. });
  90. // Array exemplars (i.e. patterned arrays)
  91. // [...] ∩ [...] <====> [...]
  92. it('Array exemplars (i.e. patterned arrays, `[...]`)', function (){
  93. given(['==='], ['===']).expect(['===']);
  94. });
  95. // Faceted dictionary exemplars
  96. // {...} ∩ {...} <====> {...}
  97. it('Faceted dictionary exemplars (`{...}`)', function (){
  98. given(LYDIA, LYDIA).expect(LYDIA);
  99. });
  100. });//</should result in an exemplar with the same type schema>
  101. });//</when intersected with the same type>
  102. // Every type intersects with "ref", with an identity result.
  103. describe('when intersected with "ref"', function (){
  104. // Simple helper used below for convenience/conciseness.
  105. var testVsRef = function (exemplar){
  106. given(exemplar, '===').expect(exemplar);
  107. given('===', exemplar).expect(exemplar);
  108. };
  109. describe('every type should result in an identity result', function (){
  110. it('"string" ∩ "ref" <====> "string"', function (){
  111. testVsRef('foo');
  112. });
  113. it('"number" ∩ "ref" <====> "number"', function (){
  114. testVsRef(-9);
  115. testVsRef(32);
  116. });
  117. it('"boolean" ∩ "ref" <====> "boolean"', function (){
  118. testVsRef(true);
  119. testVsRef(false);
  120. });
  121. it('"lamda" ∩ "ref" <====> "lamda"', function (){
  122. testVsRef('->');
  123. });
  124. it('"json" ∩ "ref" <====> "json"', function (){
  125. testVsRef('*');
  126. });
  127. it('{} ∩ "ref" <====> {}', function (){
  128. testVsRef({});
  129. });
  130. // "Generic array" exemplars (for compatibility)
  131. it(' [] ∩ "ref" <====> []', function (){
  132. testVsRef([]);
  133. });
  134. it('{...} ∩ "ref" <====> {...}', function (){
  135. testVsRef(LYDIA);
  136. });
  137. it('[...] ∩ "ref" <====> [...]', function (){
  138. testVsRef([LYDIA]);
  139. testVsRef(LYDIA.friends);
  140. });
  141. });//</every type should result in an identity result>
  142. });//</when intersected with "ref">
  143. // Now run the suite of tests in the specification directory (`spec/`).
  144. var TEST_SUITE = require('../spec/intersection.spec');
  145. _.each(TEST_SUITE, function (testDef){
  146. it('`'+util.inspect(testDef.e0, {depth: null})+'` ∩ `'+util.inspect(testDef.e1, {depth: null})+'`', function (){
  147. given(testDef.e0, testDef.e1).expect(testDef.result);
  148. });
  149. });
  150. // Additional notes:
  151. // Every type except "ref" and "lamda" intersects with "json", with an identity result.
  152. // "string" ∩ "json" <====> "string"
  153. // "number" ∩ "json" <====> "number"
  154. // "boolean" ∩ "json" <====> "boolean"
  155. // {} ∩ "json" <====> {}
  156. // [] ∩ "json" <====> []
  157. // {x:"string"} ∩ "json" <====> {x:"string"}
  158. // ["string"] ∩ "json" <====> ["string"]
  159. // Strings, numbers, booleans, and lamdas do not intersect with each other,
  160. // or with any sort of dictionary or array type.
  161. // "string" ∩ (anything else) <==/==> (ERROR)
  162. // "number" ∩ (anything else) <==/==> (ERROR)
  163. // "boolean" ∩ (anything else) <==/==> (ERROR)
  164. // "lamda" ∩ (anything else) <==/==> (ERROR)
  165. // Faceted dictionaries intersect with generic dictionaries, with an identity result.
  166. // {a:"string"} ∩ {} <====> {a:"string"}
  167. // {a:{}} ∩ {} <====> {a:{}}
  168. // Patterned arrays intersect with generic arrays, with an identity result.
  169. // ["string"] ∩ [] <====> ["string"]
  170. // [[{}]] ∩ [] <====> [[{}]]
  171. // [{}] ∩ ["string"] <====> ["string"]
  172. // Faceted dictionaries intersect with other faceted dictionaries as long as recursive
  173. // types also successfully intersect. The result is the merged schema.
  174. // (extra keys are ok, since they'll just be ignored)
  175. // {a:"string"} ∩ {a:"string",b:"string"} <====> {a:"string", b: "string"}
  176. // {x:"string"} ∩ {a:"string",b:"string"} <====> {a:"string", b: "string", x: "string"}
  177. // {x:"string", a:"number"} ∩ {a:"string",b:"string"} <==/=> (ERROR)
  178. // {x:"string", a:"json"} ∩ {a:"string",b:"string"} <====> {a:"string", b: "string", x: "string"}
  179. // Patterned arrays intersect with other patterned arrays as long as the recursive
  180. // types also successfully intersect. The result is the merged schema.
  181. // ["number"] ∩ ["json"] <====> ["number"]
  182. // ["number"] ∩ ["string"] <==/=> (ERROR)
  183. // [{a:"number"}] ∩ [{}] <====> [{a:"number"}]
  184. // Exceptions when NOT using strict validation:
  185. // "number" ∩ "string" <====> "number"
  186. // "boolean" ∩ "string" <====> "boolean"
  187. // "number" ∩ "boolean" <====> "boolean"
  188. // // Special cases:
  189. // describe('special cases', function (){
  190. // it('inside a generic dictionary keypath: should act like `json`', function (){
  191. // assert.deepEqual({ x: 'foo' }, intersection({ x: 'foo' }, {}));
  192. // assert.deepEqual({ x: 'foo' }, intersection({}, { x: 'foo' }));
  193. // });
  194. // it('inside a generic array keypath: should act like `json`', function (){
  195. // assert.deepEqual({ x: 'foo' }, intersection({ x: 'foo' }, []));
  196. // assert.deepEqual({ x: 'foo' }, intersection({ x: 'foo' }, ['*']));
  197. // assert.deepEqual({ x: 'foo' }, intersection([], { x: 'foo' }));
  198. // assert.deepEqual({ x: 'foo' }, intersection(['*'], { x: 'foo' }));
  199. // });
  200. // it('inside a JSON keypath: should act like `json`', function (){
  201. // assert.deepEqual({ x: 'foo' }, intersection({ x: 'foo' }, '*'));
  202. // assert.deepEqual({ x: 'foo' }, intersection('*', { x: 'foo' }));
  203. // });
  204. // it('inside a ref keypath: should act like `ref`', function (){
  205. // assert.deepEqual({ x: 'foo' }, intersection({ x: 'foo' }, '==='));
  206. // assert.deepEqual({ x: 'foo' }, intersection('===', { x: 'foo' }));
  207. // });
  208. // it('inside any other keypath: should throw an error', function (){
  209. // // TODO: come back to this
  210. // });
  211. // });//</special cases>
  212. });//</using exemplars & loose validation rules>
  213. });//</.intersection()>