@e22m4u/js-repository 0.1.9 → 0.1.10

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.
Files changed (118) hide show
  1. package/README.md +21 -1
  2. package/docs/assets/navigation.js +1 -1
  3. package/docs/assets/search.js +1 -1
  4. package/docs/classes/Adapter.html +1 -1
  5. package/docs/classes/AdapterLoader.html +1 -1
  6. package/docs/classes/AdapterRegistry.html +1 -1
  7. package/docs/classes/BelongsToResolver.html +1 -1
  8. package/docs/classes/DatasourceDefinitionValidator.html +1 -1
  9. package/docs/classes/DefinitionRegistry.html +1 -1
  10. package/docs/classes/EmptyValuesDefiner.html +18 -0
  11. package/docs/classes/FieldsClauseTool.html +1 -1
  12. package/docs/classes/HasManyResolver.html +1 -1
  13. package/docs/classes/HasOneResolver.html +1 -1
  14. package/docs/classes/IncludeClauseTool.html +1 -1
  15. package/docs/classes/InvalidArgumentError.html +1 -1
  16. package/docs/classes/InvalidOperatorValueError.html +1 -1
  17. package/docs/classes/ModelDataSanitizer.html +1 -1
  18. package/docs/classes/ModelDataTransformer.html +1 -1
  19. package/docs/classes/ModelDataValidator.html +1 -1
  20. package/docs/classes/ModelDefinitionUtils.html +1 -1
  21. package/docs/classes/ModelDefinitionValidator.html +1 -1
  22. package/docs/classes/NotImplementedError.html +1 -1
  23. package/docs/classes/OperatorClauseTool.html +1 -1
  24. package/docs/classes/OrderClauseTool.html +1 -1
  25. package/docs/classes/PrimaryKeysDefinitionValidator.html +1 -1
  26. package/docs/classes/PropertiesDefinitionValidator.html +1 -1
  27. package/docs/classes/PropertyTransformerRegistry.html +1 -1
  28. package/docs/classes/PropertyUniquenessValidator.html +1 -1
  29. package/docs/classes/PropertyValidatorRegistry.html +1 -1
  30. package/docs/classes/ReferencesManyResolver.html +1 -1
  31. package/docs/classes/RelationsDefinitionValidator.html +1 -1
  32. package/docs/classes/Repository.html +1 -1
  33. package/docs/classes/RepositoryRegistry.html +1 -1
  34. package/docs/classes/Schema.html +1 -1
  35. package/docs/classes/SliceClauseTool.html +1 -1
  36. package/docs/classes/WhereClauseTool.html +1 -1
  37. package/docs/enums/DataType.html +1 -1
  38. package/docs/enums/DecoratorTargetType.html +1 -1
  39. package/docs/enums/RelationType.html +1 -1
  40. package/docs/functions/capitalize.html +1 -1
  41. package/docs/functions/cloneDeep.html +1 -1
  42. package/docs/functions/excludeObjectKeys.html +1 -1
  43. package/docs/functions/getCtorName.html +1 -1
  44. package/docs/functions/getDecoratorTargetType.html +1 -1
  45. package/docs/functions/getValueByPath.html +1 -1
  46. package/docs/functions/isCtor.html +1 -1
  47. package/docs/functions/isDeepEqual.html +2 -0
  48. package/docs/functions/isPureObject.html +1 -1
  49. package/docs/functions/selectObjectKeys.html +1 -1
  50. package/docs/functions/singularize.html +1 -1
  51. package/docs/functions/stringToRegexp.html +1 -1
  52. package/docs/index.html +17 -3
  53. package/docs/interfaces/AndClause.html +1 -1
  54. package/docs/interfaces/Constructor.html +1 -1
  55. package/docs/interfaces/OrClause.html +1 -1
  56. package/docs/modules.html +4 -1
  57. package/docs/types/AnyObject.html +1 -1
  58. package/docs/types/BelongsToDefinition.html +1 -1
  59. package/docs/types/CountMethod.html +1 -1
  60. package/docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html +1 -1
  61. package/docs/types/DatasourceDefinition.html +1 -1
  62. package/docs/types/FieldsClause.html +1 -1
  63. package/docs/types/FilterClause.html +1 -1
  64. package/docs/types/Flatten.html +1 -1
  65. package/docs/types/FullPropertyDefinition.html +1 -1
  66. package/docs/types/HasManyDefinition.html +1 -1
  67. package/docs/types/HasOneDefinition.html +1 -1
  68. package/docs/types/Identity.html +1 -1
  69. package/docs/types/IncludeClause.html +1 -1
  70. package/docs/types/ItemFilterClause.html +1 -1
  71. package/docs/types/ModelData.html +1 -1
  72. package/docs/types/ModelDefinition.html +1 -1
  73. package/docs/types/ModelId.html +1 -1
  74. package/docs/types/NestedIncludeClause.html +1 -1
  75. package/docs/types/NormalizedFieldsClause.html +1 -1
  76. package/docs/types/NormalizedIncludeClause.html +1 -1
  77. package/docs/types/OperatorClause.html +1 -1
  78. package/docs/types/OptionalUnlessRequiredId.html +1 -1
  79. package/docs/types/OrderClause.html +1 -1
  80. package/docs/types/PartialBy.html +1 -1
  81. package/docs/types/PartialWithoutId.html +1 -1
  82. package/docs/types/PolyBelongsToDefinition.html +1 -1
  83. package/docs/types/PolyHasManyDefinitionWithTargetKeys.html +1 -1
  84. package/docs/types/PolyHasManyDefinitionWithTargetRelationName.html +1 -1
  85. package/docs/types/PolyHasOneDefinitionWithTargetKeys.html +1 -1
  86. package/docs/types/PolyHasOneDefinitionWithTargetRelationName.html +1 -1
  87. package/docs/types/PropertiesClause.html +1 -1
  88. package/docs/types/PropertyDefinition.html +1 -1
  89. package/docs/types/PropertyDefinitionMap.html +1 -1
  90. package/docs/types/PropertyTransformOptions.html +1 -1
  91. package/docs/types/PropertyTransformer.html +1 -1
  92. package/docs/types/PropertyTransformerContext.html +1 -1
  93. package/docs/types/PropertyUniqueness.html +2 -0
  94. package/docs/types/PropertyValidateOptions.html +1 -1
  95. package/docs/types/PropertyValidator.html +1 -1
  96. package/docs/types/PropertyValidatorContext.html +1 -1
  97. package/docs/types/ReferencesManyDefinition.html +1 -1
  98. package/docs/types/RelationDefinition.html +1 -1
  99. package/docs/types/RelationDefinitionMap.html +1 -1
  100. package/docs/types/WhereClause.html +1 -1
  101. package/docs/types/WithoutId.html +1 -1
  102. package/package.json +1 -1
  103. package/src/definition/model/properties/empty-values-definer.d.ts +23 -0
  104. package/src/definition/model/properties/empty-values-definer.js +66 -0
  105. package/src/definition/model/properties/empty-values-definer.spec.js +96 -0
  106. package/src/definition/model/properties/index.d.ts +2 -0
  107. package/src/definition/model/properties/index.js +2 -0
  108. package/src/definition/model/properties/properties-definition-validator.js +8 -2
  109. package/src/definition/model/properties/properties-definition-validator.spec.js +10 -6
  110. package/src/definition/model/properties/property-uniqueness-validator.js +10 -0
  111. package/src/definition/model/properties/property-uniqueness-validator.spec.js +44 -0
  112. package/src/definition/model/properties/property-uniqueness.d.ts +8 -0
  113. package/src/definition/model/properties/property-uniqueness.js +8 -0
  114. package/src/utils/index.d.ts +1 -0
  115. package/src/utils/index.js +1 -0
  116. package/src/utils/is-deep-equal.d.ts +10 -0
  117. package/src/utils/is-deep-equal.js +71 -0
  118. package/src/utils/is-deep-equal.spec.js +238 -0
@@ -0,0 +1,96 @@
1
+ import {expect} from 'chai';
2
+ import {DataType} from './data-type.js';
3
+ import {format} from '@e22m4u/js-format';
4
+ import {Schema} from '../../../schema.js';
5
+ import {EmptyValuesDefiner} from './empty-values-definer.js';
6
+
7
+ const getEmptyValues = (definer, dataType) => {
8
+ return definer['_emptyValuesMap'].get(dataType);
9
+ };
10
+
11
+ describe('EmptyValuesDefiner', function () {
12
+ describe('_emptyValuesMap', function () {
13
+ it('has default values', function () {
14
+ const schema = new Schema();
15
+ const S = schema.getService(EmptyValuesDefiner);
16
+ expect(Array.from(S['_emptyValuesMap'])).to.be.eql([
17
+ [DataType.ANY, [undefined, null]],
18
+ [DataType.STRING, [undefined, null, '']],
19
+ [DataType.NUMBER, [undefined, null, 0]],
20
+ [DataType.BOOLEAN, [undefined, null]],
21
+ [DataType.ARRAY, [undefined, null, []]],
22
+ [DataType.OBJECT, [undefined, null, {}]],
23
+ ]);
24
+ });
25
+ });
26
+
27
+ describe('setEmptyValuesOf', function () {
28
+ it('requires the parameter "dataType" to be a DataType enum', function () {
29
+ const schema = new Schema();
30
+ const S = schema.getService(EmptyValuesDefiner);
31
+ const throwable = v => () => S.setEmptyValuesOf(v, []);
32
+ const error = v =>
33
+ format(
34
+ 'The argument "dataType" of the EmptyValuesDefiner.setEmptyValuesOf ' +
35
+ 'must be one of data types: %l, but %s given.',
36
+ Object.values(DataType),
37
+ v,
38
+ );
39
+ expect(throwable('str')).to.throw(error('"str"'));
40
+ expect(throwable('')).to.throw(error('""'));
41
+ expect(throwable(10)).to.throw(error('10'));
42
+ expect(throwable(0)).to.throw(error('0'));
43
+ expect(throwable(true)).to.throw(error('true'));
44
+ expect(throwable(false)).to.throw(error('false'));
45
+ expect(throwable(undefined)).to.throw(error('undefined'));
46
+ expect(throwable(null)).to.throw(error('null'));
47
+ expect(throwable({})).to.throw(error('Object'));
48
+ expect(throwable([])).to.throw(error('Array'));
49
+ throwable(DataType.ANY)();
50
+ });
51
+
52
+ it('requires the parameter "emptyValues" to be an Array', function () {
53
+ const schema = new Schema();
54
+ const S = schema.getService(EmptyValuesDefiner);
55
+ const throwable = v => () => S.setEmptyValuesOf(DataType.ANY, v);
56
+ const error = v =>
57
+ format(
58
+ 'The argument "emptyValues" of the EmptyValuesDefiner.setEmptyValuesOf ' +
59
+ 'must be an Array, but %s given.',
60
+ v,
61
+ );
62
+ expect(throwable('str')).to.throw(error('"str"'));
63
+ expect(throwable('')).to.throw(error('""'));
64
+ expect(throwable(10)).to.throw(error('10'));
65
+ expect(throwable(0)).to.throw(error('0'));
66
+ expect(throwable(true)).to.throw(error('true'));
67
+ expect(throwable(false)).to.throw(error('false'));
68
+ expect(throwable(undefined)).to.throw(error('undefined'));
69
+ expect(throwable(null)).to.throw(error('null'));
70
+ expect(throwable({})).to.throw(error('Object'));
71
+ throwable([])();
72
+ throwable([1, 2, 3])();
73
+ });
74
+
75
+ it('overrides default values of the given data type', function () {
76
+ const schema = new Schema();
77
+ const S = schema.getService(EmptyValuesDefiner);
78
+ expect(getEmptyValues(S, DataType.ANY)).eql([undefined, null]);
79
+ S.setEmptyValuesOf(DataType.ANY, [1, 2, 3]);
80
+ expect(getEmptyValues(S, DataType.ANY)).eql([1, 2, 3]);
81
+ });
82
+ });
83
+
84
+ describe('isEmpty', function () {
85
+ it('returns true if the given value exists in the given type', function () {
86
+ const schema = new Schema();
87
+ const S = schema.getService(EmptyValuesDefiner);
88
+ S.setEmptyValuesOf(DataType.ANY, []);
89
+ expect(S.isEmpty(DataType.ANY, 'foo')).to.be.false;
90
+ S.setEmptyValuesOf(DataType.ANY, ['bar']);
91
+ expect(S.isEmpty(DataType.ANY, 'foo')).to.be.false;
92
+ S.setEmptyValuesOf(DataType.ANY, ['bar', 'foo']);
93
+ expect(S.isEmpty(DataType.ANY, 'foo')).to.be.true;
94
+ });
95
+ });
96
+ });
@@ -1,5 +1,7 @@
1
1
  export * from './data-type.js';
2
2
  export * from './property-definition.js';
3
+ export * from './property-uniqueness.js';
4
+ export * from './empty-values-definer.js';
3
5
  export * from './property-validator/index.js';
4
6
  export * from './property-transformer/index.js';
5
7
  export * from './property-uniqueness-validator.js';
@@ -1,5 +1,7 @@
1
1
  export * from './data-type.js';
2
2
  export * from './property-definition.js';
3
+ export * from './property-uniqueness.js';
4
+ export * from './empty-values-definer.js';
3
5
  export * from './property-validator/index.js';
4
6
  export * from './property-transformer/index.js';
5
7
  export * from './property-uniqueness-validator.js';
@@ -1,6 +1,7 @@
1
1
  import {Service} from '@e22m4u/js-service';
2
2
  import {DataType as Type} from './data-type.js';
3
3
  import {capitalize} from '../../../utils/index.js';
4
+ import {PropertyUniqueness} from './property-uniqueness.js';
4
5
  import {InvalidArgumentError} from '../../../errors/index.js';
5
6
  import {PropertyValidatorRegistry} from './property-validator/index.js';
6
7
  import {PropertyTransformerRegistry} from './property-transformer/index.js';
@@ -297,14 +298,19 @@ export class PropertiesDefinitionValidator extends Service {
297
298
  );
298
299
  }
299
300
  }
300
- if (propDef.unique && typeof propDef.unique !== 'boolean')
301
+ if (
302
+ propDef.unique &&
303
+ !Object.values(PropertyUniqueness).includes(propDef.unique)
304
+ ) {
301
305
  throw new InvalidArgumentError(
302
306
  'The provided option "unique" of the property %v in the model %v ' +
303
- 'should be a Boolean, but %v given.',
307
+ 'should be one of values: %l, but %v given.',
304
308
  propName,
305
309
  modelName,
310
+ Object.values(PropertyUniqueness),
306
311
  propDef.unique,
307
312
  );
313
+ }
308
314
  if (propDef.unique && propDef.primaryKey)
309
315
  throw new InvalidArgumentError(
310
316
  'The property %v of the model %v is a primary key, ' +
@@ -2,6 +2,7 @@ import chai from 'chai';
2
2
  import {expect} from 'chai';
3
3
  import {DataType} from './data-type.js';
4
4
  import {format} from '@e22m4u/js-format';
5
+ import {PropertyUniqueness} from './property-uniqueness.js';
5
6
  import {PropertyValidatorRegistry} from './property-validator/index.js';
6
7
  import {PropertyTransformerRegistry} from './property-transformer/index.js';
7
8
  import {PropertiesDefinitionValidator} from './properties-definition-validator.js';
@@ -490,7 +491,7 @@ describe('PropertiesDefinitionValidator', function () {
490
491
  validate({myTransformer: true})();
491
492
  });
492
493
 
493
- it('expects provided the option "unique" to be a boolean', function () {
494
+ it('expects provided the option "unique" to be the PropertyUniqueness', function () {
494
495
  const validate = v => {
495
496
  const foo = {
496
497
  type: DataType.STRING,
@@ -501,18 +502,20 @@ describe('PropertiesDefinitionValidator', function () {
501
502
  const error = v =>
502
503
  format(
503
504
  'The provided option "unique" of the property "foo" in the model "model" ' +
504
- 'should be a Boolean, but %s given.',
505
+ 'should be one of values: %l, but %s given.',
506
+ Object.values(PropertyUniqueness),
505
507
  v,
506
508
  );
507
509
  expect(validate('str')).to.throw(error('"str"'));
508
510
  expect(validate(10)).to.throw(error('10'));
509
511
  expect(validate([])).to.throw(error('Array'));
510
512
  expect(validate({})).to.throw(error('Object'));
511
- validate(true)();
512
- validate(false)();
513
+ validate(PropertyUniqueness.UNIQUE)();
514
+ validate(PropertyUniqueness.SPARSE)();
515
+ validate(PropertyUniqueness.NON_UNIQUE)();
513
516
  });
514
517
 
515
- it('expects the primary key should not have the option "unique" to be true', function () {
518
+ it('expects the primary key should not have the option "unique"', function () {
516
519
  const validate = v => () => {
517
520
  const foo = {
518
521
  type: DataType.ANY,
@@ -525,7 +528,8 @@ describe('PropertiesDefinitionValidator', function () {
525
528
  'The property "foo" of the model "model" is a primary key, ' +
526
529
  'so it should not have the option "unique" to be provided.',
527
530
  );
528
- expect(validate(true)).to.throw(error);
531
+ expect(validate(PropertyUniqueness.UNIQUE)).to.throw(error);
532
+ expect(validate(PropertyUniqueness.SPARSE)).to.throw(error);
529
533
  validate(false)();
530
534
  validate(undefined)();
531
535
  });
@@ -1,5 +1,8 @@
1
+ import {DataType} from './data-type.js';
1
2
  import {Service} from '@e22m4u/js-service';
2
3
  import {isPureObject} from '../../../utils/index.js';
4
+ import {PropertyUniqueness} from './property-uniqueness.js';
5
+ import {EmptyValuesDefiner} from './empty-values-definer.js';
3
6
  import {InvalidArgumentError} from '../../../errors/index.js';
4
7
  import {ModelDefinitionUtils} from '../model-definition-utils.js';
5
8
 
@@ -67,10 +70,17 @@ export class PropertyUniquenessValidator extends Service {
67
70
  propValue,
68
71
  );
69
72
  let willBeReplaced = undefined;
73
+ const emptyValuesDefiner = this.getService(EmptyValuesDefiner);
70
74
  for (const propName of propNames) {
71
75
  const propDef = propDefs[propName];
72
76
  if (!propDef || typeof propDef === 'string' || !propDef.unique) continue;
77
+ // sparse
73
78
  const propValue = modelData[propName];
79
+ if (propDef.unique === PropertyUniqueness.SPARSE) {
80
+ const propType = propDef.type || DataType.ANY;
81
+ const isEmpty = emptyValuesDefiner.isEmpty(propType, propValue);
82
+ if (isEmpty) continue;
83
+ }
74
84
  // create
75
85
  if (methodName === 'create') {
76
86
  const count = await countMethod({[propName]: propValue});
@@ -2,6 +2,8 @@ import {expect} from 'chai';
2
2
  import {DataType} from './data-type.js';
3
3
  import {format} from '@e22m4u/js-format';
4
4
  import {Schema} from '../../../schema.js';
5
+ import {PropertyUniqueness} from './property-uniqueness.js';
6
+ import {EmptyValuesDefiner} from './empty-values-definer.js';
5
7
  import {PropertyUniquenessValidator} from './property-uniqueness-validator.js';
6
8
  import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from '../model-definition-utils.js';
7
9
 
@@ -185,6 +187,48 @@ describe('PropertyUniquenessValidator', function () {
185
187
  );
186
188
  });
187
189
 
190
+ it('skips uniqueness checking for an empty value for "sparse" mode', async function () {
191
+ const schema = new Schema();
192
+ schema.defineModel({
193
+ name: 'model',
194
+ properties: {
195
+ foo: {
196
+ type: DataType.STRING,
197
+ unique: true,
198
+ },
199
+ bar: {
200
+ type: DataType.STRING,
201
+ unique: PropertyUniqueness.SPARSE,
202
+ },
203
+ baz: {
204
+ type: DataType.STRING,
205
+ unique: true,
206
+ },
207
+ },
208
+ });
209
+ const S = schema.getService(PropertyUniquenessValidator);
210
+ let invoked = 0;
211
+ schema
212
+ .getService(EmptyValuesDefiner)
213
+ .setEmptyValuesOf(DataType.STRING, ['val2']);
214
+ const modelData = {
215
+ foo: 'val1',
216
+ bar: 'val2',
217
+ baz: 'val3',
218
+ };
219
+ const countMethod = where => {
220
+ invoked++;
221
+ if (invoked === 1) {
222
+ expect(where).to.be.eql({foo: 'val1'});
223
+ } else if (invoked === 2) {
224
+ expect(where).to.be.eql({baz: 'val3'});
225
+ }
226
+ return 0;
227
+ };
228
+ await S.validate(countMethod, 'create', 'model', modelData);
229
+ expect(invoked).to.be.eql(2);
230
+ });
231
+
188
232
  describe('create', function () {
189
233
  it('throws an error if the "countMethod" returns a positive number', async function () {
190
234
  const schema = new Schema();
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Property uniqueness.
3
+ */
4
+ export declare type PropertyUniqueness = {
5
+ UNIQUE: true;
6
+ SPARSE: 'sparse';
7
+ NON_UNIQUE: false;
8
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Property uniqueness.
3
+ */
4
+ export const PropertyUniqueness = {
5
+ UNIQUE: true,
6
+ SPARSE: 'sparse',
7
+ NON_UNIQUE: false,
8
+ };
@@ -2,6 +2,7 @@ export * from './is-ctor.js';
2
2
  export * from './capitalize.js';
3
3
  export * from './clone-deep.js';
4
4
  export * from './singularize.js';
5
+ export * from './is-deep-equal.js';
5
6
  export * from './get-ctor-name.js';
6
7
  export * from './is-pure-object.js';
7
8
  export * from './string-to-regexp.js';
@@ -2,6 +2,7 @@ export * from './is-ctor.js';
2
2
  export * from './capitalize.js';
3
3
  export * from './clone-deep.js';
4
4
  export * from './singularize.js';
5
+ export * from './is-deep-equal.js';
5
6
  export * from './get-ctor-name.js';
6
7
  export * from './is-pure-object.js';
7
8
  export * from './string-to-regexp.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Is deep equal.
3
+ *
4
+ * @param firstValue
5
+ * @param secondValue
6
+ */
7
+ export declare function isDeepEqual(
8
+ firstValue: unknown,
9
+ secondValue: unknown,
10
+ ): boolean;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Is deep equal.
3
+ * https://github.com/pinglu85/BFEdevSolutions/blob/main/Coding-Problems/69.implement-deep-equal-isEqual.md
4
+ *
5
+ * @param {*} firstValue
6
+ * @param {*} secondValue
7
+ * @returns {boolean}
8
+ */
9
+ export function isDeepEqual(firstValue, secondValue) {
10
+ const cached = new WeakMap();
11
+ const compare = (a, b) => {
12
+ // Check if one of the two inputs is primitive by using typeof
13
+ // operator; since the typeof primitive null is object, check
14
+ // if one of the inputs is equal to null. If one of the two
15
+ // inputs is primitive, then I can compare them by reference.
16
+ if (a === null || b === null) return a === b;
17
+ if (typeof a !== 'object' || typeof b !== 'object') return a === b;
18
+ // Check if the data type of the two inputs are the same,
19
+ // both are arrays or objects. If they are not, return false.
20
+ const dataTypeA = Array.isArray(a) ? 'array' : 'object';
21
+ const dataTypeB = Array.isArray(b) ? 'array' : 'object';
22
+ if (dataTypeA !== dataTypeB) return false;
23
+ // Use Object.keys and Object.getOwnPropertySymbols to get
24
+ // all of enumerable and not-inherited properties of the two
25
+ // inputs. Compare their size respectively, if one of them
26
+ // is not equal, return false.
27
+ const keysA = Object.keys(a);
28
+ const keysB = Object.keys(b);
29
+ if (keysA.length !== keysB.length) return false;
30
+ const symbolsA = Object.getOwnPropertySymbols(a);
31
+ const symbolsB = Object.getOwnPropertySymbols(b);
32
+ if (symbolsA.length !== symbolsB.length) return false;
33
+ // To handle the circular reference, initialize a WeakMap
34
+ // that is going to keep track of the objects or arrays
35
+ // that have been seen, in which each key is an object
36
+ // or an array and each value is a set of objects or arrays,
37
+ // that have been compared to that object or array.
38
+ let setForA = cached.get(a);
39
+ if (setForA == null) {
40
+ setForA = new Set();
41
+ cached.set(a, setForA);
42
+ } else if (setForA.has(b)) {
43
+ return true;
44
+ }
45
+ setForA.add(b);
46
+ let setForB = cached.get(b);
47
+ if (setForB == null) {
48
+ setForB = new Set();
49
+ cached.set(b, setForB);
50
+ } else if (setForB.has(a)) {
51
+ return true;
52
+ }
53
+ setForB.add(a);
54
+ // Compare the property names and the values. Loop through
55
+ // all the properties of the first input data, check if
56
+ // the property name also exist in the second input data,
57
+ // if not, return false; otherwise recursively compare
58
+ // the property value.
59
+ const propertyNamesA = [...keysA, ...symbolsA];
60
+ for (const propertyNameA of propertyNamesA) {
61
+ if (!b.hasOwnProperty(propertyNameA)) return false;
62
+ const propertyValueA = a[propertyNameA];
63
+ const propertyValueB = b[propertyNameA];
64
+ if (!compare(propertyValueA, propertyValueB)) return false;
65
+ }
66
+ // If we get out of the loop without
67
+ // returning false, return true.
68
+ return true;
69
+ };
70
+ return compare(firstValue, secondValue);
71
+ }
@@ -0,0 +1,238 @@
1
+ import {expect} from 'chai';
2
+ import {isDeepEqual} from './is-deep-equal.js';
3
+
4
+ const check = (a, b, expected) => {
5
+ expect(isDeepEqual(a, b)).to.be.eq(expected);
6
+ expect(isDeepEqual(b, a)).to.be.eq(expected);
7
+ };
8
+
9
+ describe('isDeepEqual', function () {
10
+ describe('string', function () {
11
+ it('a non-empty string', function () {
12
+ check('str', 'str', true);
13
+ check('str', '', false);
14
+ check('str', 10, false);
15
+ check('str', 0, false);
16
+ check('str', -10, false);
17
+ check('str', true, false);
18
+ check('str', false, false);
19
+ check('str', undefined, false);
20
+ check('str', null, false);
21
+ check('str', {foo: 'bar'}, false);
22
+ check('str', {}, false);
23
+ check('str', [1, 2, 3], false);
24
+ check('str', [], false);
25
+ });
26
+
27
+ it('an empty string', function () {
28
+ check('', 'str', false);
29
+ check('', '', true);
30
+ check('', 10, false);
31
+ check('', 0, false);
32
+ check('', -10, false);
33
+ check('', true, false);
34
+ check('', false, false);
35
+ check('', undefined, false);
36
+ check('', null, false);
37
+ check('', {foo: 'bar'}, false);
38
+ check('', {}, false);
39
+ check('', [1, 2, 3], false);
40
+ check('', [], false);
41
+ });
42
+ });
43
+
44
+ describe('number', function () {
45
+ it('a positive number', function () {
46
+ check(10, 'str', false);
47
+ check(10, '', false);
48
+ check(10, 10, true);
49
+ check(10, 0, false);
50
+ check(10, -10, false);
51
+ check(10, true, false);
52
+ check(10, false, false);
53
+ check(10, undefined, false);
54
+ check(10, null, false);
55
+ check(10, {foo: 'bar'}, false);
56
+ check(10, {}, false);
57
+ check(10, [1, 2, 3], false);
58
+ check(10, [], false);
59
+ });
60
+
61
+ it('zero', function () {
62
+ check(0, 'str', false);
63
+ check(0, '', false);
64
+ check(0, 10, false);
65
+ check(0, 0, true);
66
+ check(0, -10, false);
67
+ check(0, true, false);
68
+ check(0, false, false);
69
+ check(0, undefined, false);
70
+ check(0, null, false);
71
+ check(0, {foo: 'bar'}, false);
72
+ check(0, {}, false);
73
+ check(0, [1, 2, 3], false);
74
+ check(0, [], false);
75
+ });
76
+
77
+ it('a negative number', function () {
78
+ check(-10, 'str', false);
79
+ check(-10, '', false);
80
+ check(-10, 10, false);
81
+ check(-10, 0, false);
82
+ check(-10, -10, true);
83
+ check(-10, true, false);
84
+ check(-10, false, false);
85
+ check(-10, undefined, false);
86
+ check(-10, null, false);
87
+ check(-10, {foo: 'bar'}, false);
88
+ check(-10, {}, false);
89
+ check(-10, [1, 2, 3], false);
90
+ check(-10, [], false);
91
+ });
92
+ });
93
+
94
+ describe('boolean', function () {
95
+ it('true', function () {
96
+ check(true, 'str', false);
97
+ check(true, '', false);
98
+ check(true, 10, false);
99
+ check(true, 0, false);
100
+ check(true, -10, false);
101
+ check(true, true, true);
102
+ check(true, false, false);
103
+ check(true, undefined, false);
104
+ check(true, null, false);
105
+ check(true, {foo: 'bar'}, false);
106
+ check(true, {}, false);
107
+ check(true, [1, 2, 3], false);
108
+ check(true, [], false);
109
+ });
110
+
111
+ it('false', function () {
112
+ check(false, 'str', false);
113
+ check(false, '', false);
114
+ check(false, 10, false);
115
+ check(false, 0, false);
116
+ check(false, -10, false);
117
+ check(false, true, false);
118
+ check(false, false, true);
119
+ check(false, undefined, false);
120
+ check(false, null, false);
121
+ check(false, {foo: 'bar'}, false);
122
+ check(false, {}, false);
123
+ check(false, [1, 2, 3], false);
124
+ check(false, [], false);
125
+ });
126
+ });
127
+
128
+ describe('array', function () {
129
+ it('an array of numbers', function () {
130
+ check([1, 2, 3], 'str', false);
131
+ check([1, 2, 3], '', false);
132
+ check([1, 2, 3], 10, false);
133
+ check([1, 2, 3], 0, false);
134
+ check([1, 2, 3], -10, false);
135
+ check([1, 2, 3], true, false);
136
+ check([1, 2, 3], false, false);
137
+ check([1, 2, 3], undefined, false);
138
+ check([1, 2, 3], null, false);
139
+ check([1, 2, 3], {foo: 'bar'}, false);
140
+ check([1, 2, 3], {}, false);
141
+ check([1, 2, 3], [1, 2, 3], true);
142
+ check([1, 2, 3], [], false);
143
+ });
144
+
145
+ it('an empty array', function () {
146
+ check([], 'str', false);
147
+ check([], '', false);
148
+ check([], 10, false);
149
+ check([], 0, false);
150
+ check([], -10, false);
151
+ check([], true, false);
152
+ check([], false, false);
153
+ check([], undefined, false);
154
+ check([], null, false);
155
+ check([], {foo: 'bar'}, false);
156
+ check([], {}, false);
157
+ check([], [1, 2, 3], false);
158
+ check([], [], true);
159
+ });
160
+ });
161
+
162
+ describe('object', function () {
163
+ it('string key and string value', function () {
164
+ check({foo: 'bar'}, 'str', false);
165
+ check({foo: 'bar'}, '', false);
166
+ check({foo: 'bar'}, 10, false);
167
+ check({foo: 'bar'}, 0, false);
168
+ check({foo: 'bar'}, -10, false);
169
+ check({foo: 'bar'}, true, false);
170
+ check({foo: 'bar'}, false, false);
171
+ check({foo: 'bar'}, undefined, false);
172
+ check({foo: 'bar'}, null, false);
173
+ check({foo: 'bar'}, {foo: 'bar'}, true);
174
+ check({foo: 'bar'}, {}, false);
175
+ check({foo: 'bar'}, [1, 2, 3], false);
176
+ check({foo: 'bar'}, [], false);
177
+ });
178
+
179
+ it('an empty object', function () {
180
+ check({}, 'str', false);
181
+ check({}, '', false);
182
+ check({}, 10, false);
183
+ check({}, 0, false);
184
+ check({}, -10, false);
185
+ check({}, true, false);
186
+ check({}, false, false);
187
+ check({}, undefined, false);
188
+ check({}, null, false);
189
+ check({}, {foo: 'bar'}, false);
190
+ check({}, {}, true);
191
+ check({}, [1, 2, 3], false);
192
+ check({}, [], false);
193
+ });
194
+
195
+ it('null', function () {
196
+ check(null, 'str', false);
197
+ check(null, '', false);
198
+ check(null, 10, false);
199
+ check(null, 0, false);
200
+ check(null, -10, false);
201
+ check(null, true, false);
202
+ check(null, false, false);
203
+ check(null, undefined, false);
204
+ check(null, null, true);
205
+ check(null, {foo: 'bar'}, false);
206
+ check(null, {}, false);
207
+ check(null, [1, 2, 3], false);
208
+ check(null, [], false);
209
+ });
210
+
211
+ it('circular reference to itself', function () {
212
+ const a = {foo: 'bar'};
213
+ const b = {baz: 'qux'};
214
+ const c = {foo: 'bar'};
215
+ a.itself = a;
216
+ b.itself = b;
217
+ c.itself = c;
218
+ check(a, b, false);
219
+ check(a, c, true);
220
+ });
221
+ });
222
+
223
+ it('undefined', function () {
224
+ check(undefined, 'str', false);
225
+ check(undefined, '', false);
226
+ check(undefined, 10, false);
227
+ check(undefined, 0, false);
228
+ check(undefined, -10, false);
229
+ check(undefined, true, false);
230
+ check(undefined, false, false);
231
+ check(undefined, undefined, true);
232
+ check(undefined, null, false);
233
+ check(undefined, {foo: 'bar'}, false);
234
+ check(undefined, {}, false);
235
+ check(undefined, [1, 2, 3], false);
236
+ check(undefined, [], false);
237
+ });
238
+ });