@onehat/data 1.17.2 → 1.17.3

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.
@@ -155,7 +155,7 @@ describe('Config options', function() {
155
155
 
156
156
  });
157
157
 
158
- it.only('Replace this.method with config.method', function() {
158
+ it('Replace this.method with config.method', function() {
159
159
 
160
160
  class Base {
161
161
  constructor(config = {}) {
@@ -471,11 +471,11 @@ describe('Entity', function() {
471
471
  });
472
472
 
473
473
  it('hash', function() {
474
- expect(this.entity.hash).to.be.eq(4635951664292355);
474
+ expect(this.entity.hash).to.be.eq(5365087438356619);
475
475
 
476
476
  // change a property & check again
477
477
  this.entity.foo = 3;
478
- expect(this.entity.hash).to.be.eq(1510366977941323);
478
+ expect(this.entity.hash).to.be.eq(358445507972157);
479
479
  });
480
480
 
481
481
  });
@@ -900,4 +900,86 @@ describe('Entity', function() {
900
900
  });
901
901
  });
902
902
 
903
+ describe('tree', function() {
904
+
905
+ // Needed for all tree tests...
906
+ const
907
+ schema = new Schema({
908
+ name: 'nodes',
909
+ model: {
910
+ idProperty: 'id',
911
+ displayProperty: 'display',
912
+ parentIdProperty: 'parent_id',
913
+ depthProperty: 'depth',
914
+ hasChildrenProperty: 'hasChildren',
915
+ isTree: true,
916
+ isClosureTable: true,
917
+ properties: [
918
+ { name: 'id', type: 'int' },
919
+ { name: 'display' },
920
+ { name: 'parent_id', type: 'int' },
921
+ { name: 'depth', type: 'int' },
922
+ { name: 'hasChildren', type: 'bool' },
923
+ ],
924
+ },
925
+ }),
926
+ data = {
927
+ id: 2,
928
+ display: 'Child 1',
929
+ parent_id: 1,
930
+ depth: 1,
931
+ hasChildren: true,
932
+ },
933
+ creatEntity = async () => {
934
+ const entity = new Entity(schema, data);
935
+ entity.initialize();
936
+ return entity;
937
+ },
938
+ destoryEntity = (entity) => {
939
+ entity.destroy();
940
+ };
941
+
942
+ it('magic properties', function() {
943
+ (async () => {
944
+ const entity = await creatEntity();
945
+
946
+ expect(entity.parentId).to.be.eq(1);
947
+ expect(entity.depth).to.be.eq(1);
948
+ expect(entity.hasChildren).to.be.true;
949
+
950
+ destoryEntity(entity);
951
+ })();
952
+ });
953
+
954
+ it('parents/children', function() {
955
+ (async () => {
956
+ const entity = await creatEntity();
957
+
958
+ const parent = this.entity;
959
+ entity.parent = parent;
960
+ expect(entity.parent).to.be.eq(parent);
961
+
962
+ const children = [this.entity];
963
+ entity.children = children;
964
+ expect(entity.children).to.be.eq(children);
965
+
966
+ destoryEntity(entity);
967
+ })();
968
+ });
969
+
970
+ it('ensureTree', function() {
971
+ (async () => {
972
+ const entity = await creatEntity();
973
+
974
+ expect(entity.ensureTree()).to.be.true;
975
+
976
+ entity.isTree = false;
977
+ expect(entity.ensureTree()).to.be.false;
978
+
979
+ destoryEntity(entity);
980
+ })();
981
+ });
982
+
983
+ });
984
+
903
985
  });
@@ -91,7 +91,7 @@ describe('OneBuildRepository', function() {
91
91
  r.setValuelessParam('conditions[field IS NOT NULL]');
92
92
 
93
93
  expect(r._params.conditions.field).to.be.eq(1);
94
- expect(r._params.conditions[0]).to.be.eq('field IS NOT NULL');
94
+ expect(r._params.conditions.undefined).to.be.eq('field IS NOT NULL');
95
95
  });
96
96
 
97
97
  it('clearParams', function() {
@@ -281,7 +281,7 @@ describe('MemoryRepository', function() {
281
281
  expect(_.size(this.repository.getRawValues())).to.be.eq(5);
282
282
  expect(_.size(this.repository.getOriginalData())).to.be.eq(5);
283
283
  expect(_.size(this.repository.getParsedValues())).to.be.eq(5);
284
- expect(this.repository.getByIx(2).value).to.be.eq('five');
284
+ expect(this.repository.getByIx(4).value).to.be.eq('five');
285
285
  expect(_.size(this.repository.getNonPersisted())).to.be.eq(0);
286
286
  expect(_.size(this.repository.getPhantom())).to.be.eq(0);
287
287
  expect(_.size(this.repository.getDirty())).to.be.eq(0);
@@ -66,12 +66,21 @@ describe('OneBuildRepository', function() {
66
66
  it('401', function() {
67
67
  cy.wrap((async () => {
68
68
 
69
- let loggedOut = false;
69
+ let loggedOut = false,
70
+ isError = false;
70
71
  this.oneHatData.on('logout', function() { // NOTE: We are listening to the global oneHatData object, not just the individual repository
71
72
  loggedOut = true;
72
73
  });
73
- await this.repository.load();
74
- expect(loggedOut).to.be.true;
74
+ try {
75
+ await this.repository.load();
76
+ } catch(e) {
77
+ isError = true;
78
+ }
79
+ if (!isError) {
80
+ expect(loggedOut).to.be.true;
81
+ } else {
82
+ throw Error('404 error! Maybe set baseUrl?')
83
+ }
75
84
 
76
85
  })());
77
86
  });
@@ -1050,7 +1050,7 @@ describe('Repository Base', function() {
1050
1050
 
1051
1051
  });
1052
1052
 
1053
- describe('options', function() {
1053
+ describe('misc', function() {
1054
1054
  it('setOptions', function() {
1055
1055
 
1056
1056
  this.repository.setOptions({
@@ -1061,11 +1061,232 @@ describe('Repository Base', function() {
1061
1061
 
1062
1062
  expect(this.repository.api.baseURL).to.be.eq('test123');
1063
1063
  });
1064
+
1065
+ it('toString', function() {
1066
+ const str = this.repository.toString();
1067
+ expect(str).to.be.eq('NullRepository {bar} - foo');
1068
+ });
1064
1069
  });
1065
1070
 
1066
- it('toString', function() {
1067
- const str = this.repository.toString();
1068
- expect(str).to.be.eq('NullRepository {bar} - foo');
1071
+ describe('tree', function() {
1072
+
1073
+ // Needed for all tree tests...
1074
+ const
1075
+ schema = new Schema({
1076
+ name: 'nodes',
1077
+ model: {
1078
+ idProperty: 'id',
1079
+ displayProperty: 'display',
1080
+ parentIdProperty: 'parent_id',
1081
+ depthProperty: 'depth',
1082
+ hasChildrenProperty: 'hasChildren',
1083
+ isTree: true,
1084
+ isClosureTable: true,
1085
+ properties: [
1086
+ { name: 'id', type: 'int', },
1087
+ { name: 'display', },
1088
+ { name: 'parent_id', type: 'int', },
1089
+ { name: 'depth', type: 'int', },
1090
+ { name: 'hasChildren', type: 'bool', },
1091
+ ],
1092
+ },
1093
+ repository: 'memory',
1094
+ }),
1095
+ Repository = RepositoryTypes.memory,
1096
+ data = [
1097
+ {
1098
+ id: 1,
1099
+ display: 'Root',
1100
+ parent_id: null,
1101
+ depth: 0,
1102
+ hasChildren: true,
1103
+ isChildrenLoaded: true,
1104
+ },
1105
+ {
1106
+ id: 2,
1107
+ display: 'Child 1',
1108
+ parent_id: 1,
1109
+ depth: 1,
1110
+ hasChildren: true,
1111
+ isChildrenLoaded: true,
1112
+ },
1113
+ {
1114
+ id: 3,
1115
+ display: 'Child 2',
1116
+ parent_id: 1,
1117
+ depth: 1,
1118
+ isChildrenLoaded: true,
1119
+ },
1120
+ {
1121
+ id: 4,
1122
+ display: 'Grandchild',
1123
+ parent_id: 2,
1124
+ depth: 2,
1125
+ isChildrenLoaded: true,
1126
+ },
1127
+ ],
1128
+ creatRepository = async () => {
1129
+ const repository = new Repository({
1130
+ id: 'tree',
1131
+ schema,
1132
+ isAutoLoad: true,
1133
+ isAutoSave: true,
1134
+ isPaginated: true,
1135
+ data,
1136
+ });
1137
+ await repository.initialize();
1138
+ return repository;
1139
+ },
1140
+ destoryRepository = (repository) => {
1141
+ repository.destroy();
1142
+ };
1143
+
1144
+ it('TreeNode (Entity) tests', function() {
1145
+ (async () => {
1146
+ const repository = await creatRepository();
1147
+ repository.assembleTreeNodes();
1148
+
1149
+ const
1150
+ id1 = repository.getById(1),
1151
+ id2 = repository.getById(2),
1152
+ id3 = repository.getById(3),
1153
+ id4 = repository.getById(4);
1154
+
1155
+ // hasChildren
1156
+ expect(id2.hasChildren).to.be.true;
1157
+ expect(id4.hasChildren).to.be.false;
1158
+
1159
+ // getParent
1160
+ expect(id2.getParent()).to.be.eq(id1);
1161
+ expect(id1.getParent()).to.be.null;
1162
+
1163
+ // getChildren
1164
+ let children = await id2.getChildren();
1165
+ expect(children).to.be.eql([id4]);
1166
+
1167
+ children = await id3.getChildren();
1168
+ expect(children).to.be.empty;
1169
+
1170
+ // hasThisChild
1171
+ let hasThisChild = await id2.hasThisChild(id3);
1172
+ expect(hasThisChild).to.be.false;
1173
+
1174
+ hasThisChild = await id2.hasThisChild(id4);
1175
+ expect(hasThisChild).to.be.true;
1176
+
1177
+ // loadChildren (& reloadChildren)
1178
+ // this needs to be on OneBuild spec
1179
+
1180
+ // getPrevousSibling
1181
+ let sibling = await id2.getPrevousSibling();
1182
+ expect(sibling).to.be.null;
1183
+
1184
+ sibling = await id3.getPrevousSibling();
1185
+ expect(sibling).to.be.eq(id2);
1186
+
1187
+ sibling = await id3.getNextSibling();
1188
+ expect(sibling).to.be.null;
1189
+
1190
+ sibling = await id2.getNextSibling();
1191
+ expect(sibling).to.be.eq(id3);
1192
+
1193
+ // getChildAt
1194
+ let child = await id1.getChildAt(0);
1195
+ expect(child).to.be.eq(id2);
1196
+
1197
+ child = await id1.getChildAt(1);
1198
+ expect(child).to.be.eq(id3);
1199
+
1200
+ child = await id1.getChildAt(2);
1201
+ expect(child).to.be.null;
1202
+
1203
+ // getFirstChild
1204
+ child = await id1.getFirstChild();
1205
+ expect(child).to.be.eq(id2);
1206
+
1207
+ child = await id4.getFirstChild();
1208
+ expect(child).to.be.null;
1209
+
1210
+ // getLastChild
1211
+ child = await id1.getLastChild();
1212
+ expect(child).to.be.eq(id3);
1213
+
1214
+ child = await id4.getLastChild();
1215
+ expect(child).to.be.null;
1216
+
1217
+ destoryRepository(repository);
1218
+ })();
1219
+ });
1220
+
1221
+ it('getRootNodes', function() {
1222
+ (async () => {
1223
+ const repository = await creatRepository();
1224
+ repository.assembleTreeNodes();
1225
+
1226
+ const
1227
+ id1 = repository.getById(1);
1228
+
1229
+ const rootNodes = repository.getRootNodes();
1230
+ expect(rootNodes).to.be.eql([id1]);
1231
+
1232
+ destoryRepository(repository);
1233
+ })();
1234
+ });
1235
+
1236
+ it('assembleTreeNodes', function() {
1237
+ (async () => {
1238
+ const repository = await creatRepository();
1239
+
1240
+ const
1241
+ id1 = repository.getById(1),
1242
+ id2 = repository.getById(2),
1243
+ id3 = repository.getById(3),
1244
+ id4 = repository.getById(4);
1245
+
1246
+ // Verify no children or parents
1247
+ expect(id2.parent).to.be.null;
1248
+ expect(id2.children).to.be.empty;
1249
+
1250
+ repository.assembleTreeNodes();
1251
+
1252
+ // Now check that they are populated
1253
+ expect(id2.parent).to.be.eq(id1);
1254
+ expect(id2.children).to.be.eql([id4]);
1255
+
1256
+ // Re-assemble them
1257
+ repository.assembleTreeNodes();
1258
+
1259
+ // check them again
1260
+ expect(id2.parent).to.be.eq(id1);
1261
+ expect(id2.children).to.be.eql([id4]);
1262
+
1263
+ destoryRepository(repository);
1264
+ })();
1265
+ });
1266
+
1267
+ it('removeTreeNode', function() {
1268
+ (async () => {
1269
+ const repository = await creatRepository();
1270
+ repository.assembleTreeNodes();
1271
+
1272
+ const
1273
+ id1 = repository.getById(1),
1274
+ id2 = repository.getById(2),
1275
+ id3 = repository.getById(3),
1276
+ id4 = repository.getById(4);
1277
+
1278
+ const before = repository.getEntities();
1279
+ expect(before.length).to.be.eq(4);
1280
+
1281
+ repository.removeTreeNode(id2);
1282
+
1283
+ const after = repository.getEntities();
1284
+ expect(after.length).to.be.eq(2);
1285
+
1286
+ destoryRepository(repository);
1287
+ })();
1288
+ });
1289
+
1069
1290
  });
1070
1291
 
1071
1292
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/data",
3
- "version": "1.17.2",
3
+ "version": "1.17.3",
4
4
  "description": "JS data modeling package with adapters for many storage mediums.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -35,7 +35,7 @@ class Entity extends EventEmitter {
35
35
  * @param {boolean} originalIsMapped - Has data already been mapped according to schema?
36
36
  * @param {boolean} isDelayedSave - Should the repository skip autosave when immediately adding the record?
37
37
  */
38
- constructor(schema, rawData = {}, repository = null, originalIsMapped = false, isDelayedSave = false, isRemotePhantomMode = false, isRoot = false) {
38
+ constructor(schema, rawData = {}, repository = null, originalIsMapped = false, isDelayedSave = false, isRemotePhantomMode = false) {
39
39
  super(...arguments);
40
40
 
41
41
  if (!schema) {
@@ -118,12 +118,6 @@ class Entity extends EventEmitter {
118
118
  throw new Error('hasChildrenProperty cannot be empty for a TreeNode');
119
119
  }
120
120
 
121
- /**
122
- * @member {boolean} isRoot - Whether this TreeNode is the root TreeNode
123
- * For trees only
124
- */
125
- this.isRoot = false;
126
-
127
121
  /**
128
122
  * @member {TreeNode} parent - The parent TreeNode for this TreeNode
129
123
  * For trees only
@@ -1504,7 +1498,7 @@ class Entity extends EventEmitter {
1504
1498
  throw Error('this.getParentId is no longer valid. TreeNode has been destroyed.');
1505
1499
  }
1506
1500
 
1507
- return this.geParentIdProperty().getSubmitValue();
1501
+ return this.getParentIdProperty().getSubmitValue();
1508
1502
  }
1509
1503
 
1510
1504
  /**
@@ -1517,13 +1511,24 @@ class Entity extends EventEmitter {
1517
1511
 
1518
1512
  /**
1519
1513
  * Getter of hasParent
1520
- * Returns true if this is not a root node
1514
+ * Returns true if this node has a parentId
1521
1515
  * @return {boolean} hasParent
1522
1516
  */
1523
1517
  get hasParent() {
1524
1518
  this.ensureTree();
1525
1519
 
1526
- return !this.isRoot;
1520
+ return !!this.parentId;
1521
+ }
1522
+
1523
+ /**
1524
+ * Getter of isRoot
1525
+ * Returns true if this node has no parent
1526
+ * @return {boolean} hasParent
1527
+ */
1528
+ get isRoot() {
1529
+ this.ensureTree();
1530
+
1531
+ return !this.hasParent;
1527
1532
  }
1528
1533
 
1529
1534
  /**
@@ -1626,13 +1631,26 @@ class Entity extends EventEmitter {
1626
1631
  if (this.isDestroyed) {
1627
1632
  throw Error('this.getChildren is no longer valid. TreeNode has been destroyed.');
1628
1633
  }
1629
-
1630
1634
  if (!this.isChildrenLoaded) {
1631
1635
  await this.loadChildren();
1632
1636
  }
1633
1637
  return this.children;
1634
1638
  }
1635
1639
 
1640
+ /**
1641
+ * Whether the supplied TreeNode is a child of this TreeNode.
1642
+ * @return {boolean} hasThisChild
1643
+ */
1644
+ hasThisChild = async (treeNode) => {
1645
+ this.ensureTree();
1646
+ if (this.isDestroyed) {
1647
+ throw Error('this.hasThisChild is no longer valid. TreeNode has been destroyed.');
1648
+ }
1649
+
1650
+ const children = await this.getChildren();
1651
+ return _.includes(children, treeNode);
1652
+ }
1653
+
1636
1654
  /**
1637
1655
  * Loads the children of this TreeNode from repository.
1638
1656
  */
@@ -1669,9 +1687,9 @@ class Entity extends EventEmitter {
1669
1687
  const
1670
1688
  parent = this.getParent(),
1671
1689
  siblings = await parent.getChildren();
1672
- let previous;
1690
+ let previous = null;
1673
1691
  _.each(siblings, (treeNode) => {
1674
- if (treeNode === this) {
1692
+ if (treeNode.id === this.id) {
1675
1693
  return false;
1676
1694
  }
1677
1695
  previous = treeNode;
@@ -1699,7 +1717,7 @@ class Entity extends EventEmitter {
1699
1717
  next = treeNode;
1700
1718
  return false;
1701
1719
  }
1702
- if (treeNode === this) {
1720
+ if (treeNode.id === this.id) {
1703
1721
  returnNext = true;
1704
1722
  }
1705
1723
  })
@@ -1716,6 +1734,9 @@ class Entity extends EventEmitter {
1716
1734
  throw Error('this.getChildAt is no longer valid. TreeNode has been destroyed.');
1717
1735
  }
1718
1736
 
1737
+ if (!this.children[ix]) {
1738
+ return null;
1739
+ }
1719
1740
  return this.children[ix];
1720
1741
  }
1721
1742
 
@@ -1729,6 +1750,9 @@ class Entity extends EventEmitter {
1729
1750
  throw Error('this.getFirstChild is no longer valid. TreeNode has been destroyed.');
1730
1751
  }
1731
1752
 
1753
+ if (!this.children[0]) {
1754
+ return null;
1755
+ }
1732
1756
  return this.children[0];
1733
1757
  }
1734
1758
 
@@ -1742,7 +1766,11 @@ class Entity extends EventEmitter {
1742
1766
  throw Error('this.getLastChild is no longer valid. TreeNode has been destroyed.');
1743
1767
  }
1744
1768
 
1745
- return this.children.slice(-1)[0]
1769
+ const child = this.children.slice(-1)[0];
1770
+ if (!child) {
1771
+ return null;
1772
+ }
1773
+ return child;
1746
1774
  }
1747
1775
 
1748
1776
  /**
@@ -1751,8 +1779,10 @@ class Entity extends EventEmitter {
1751
1779
  */
1752
1780
  ensureTree = async () => {
1753
1781
  if (!this.isTree) {
1754
- this.throwError('This Repository is not a tree!');
1782
+ this.throwError('This Entity is not a tree!');
1783
+ return false;
1755
1784
  }
1785
+ return true;
1756
1786
  }
1757
1787
 
1758
1788
 
@@ -511,13 +511,9 @@ class MemoryRepository extends Repository {
511
511
 
512
512
  super.removeEntity(entity);
513
513
 
514
- if (this.hasSorters) {
515
- this._applySorters();
516
- }
517
- if (this.hasFilters) {
518
- this._applyFilters();
519
- }
520
514
  delete this._keyedEntities[id];
515
+
516
+ this._recalculate();
521
517
  }
522
518
 
523
519
 
@@ -995,7 +995,7 @@ export default class Repository extends EventEmitter {
995
995
  entity = Repository._createEntity(this.schema, data, this, isPersisted, originalIsMapped, isDelayedSave, this.isRemotePhantomMode);
996
996
  }
997
997
  this._relayEntityEvents(entity);
998
- this.entities.unshift(entity);
998
+ this.entities.unshift(entity); // Add to *beginning* of entities array, so the phantom record will appear at the beginning of the current page
999
999
 
1000
1000
  // Create id if needed
1001
1001
  if (!this.isRemotePhantomMode && entity.isPhantom) {
@@ -1056,11 +1056,13 @@ export default class Repository extends EventEmitter {
1056
1056
  addMultiple = async (allData, isPersisted = false, originalIsMapped = false) => {
1057
1057
 
1058
1058
  let entities = [],
1059
- i;
1059
+ i,
1060
+ data,
1061
+ entity;
1060
1062
 
1061
1063
  for (i = 0; i < allData.length; i++) {
1062
- const data = allData[i],
1063
- entity = await this.add(data, isPersisted, originalIsMapped);
1064
+ data = allData[i];
1065
+ entity = await this.add(data, isPersisted, originalIsMapped);
1064
1066
  entities.push(entity);
1065
1067
  };
1066
1068
 
@@ -2013,7 +2015,9 @@ export default class Repository extends EventEmitter {
2013
2015
  ensureTree = async () => {
2014
2016
  if (!this.isTree) {
2015
2017
  this.throwError('This Repository is not a tree!');
2018
+ return false;
2016
2019
  }
2020
+ return true;
2017
2021
  }
2018
2022
 
2019
2023