neo.mjs 4.0.71 → 4.0.74

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.
package/README.md CHANGED
@@ -235,8 +235,9 @@ More infos: <a href="./BACKERS.md">Sponsors & Backers</a>
235
235
 
236
236
  </br></br>
237
237
  <h2 id="jobs">14. Jobs</h2>
238
- Accenture is hiring multiple neo.mjs developers for the new Cloud Technology Studio in Kaiserslauern (Germany):</br>
239
- </br>
238
+ Accenture is hiring multiple neo.mjs developers for the new Cloud Technology Studio in Kaiserslauern (Germany):
239
+ <a href="https://www.accenture.com/de-de/careers/jobdetails?id=R00057924_de">Senior neo.mjs Frontend Developer /Architect (all genders)</a></br></br>
240
+
240
241
  These full-time roles are based on German contracts, so they require living in (or relocating to) Germany.
241
242
  Ping us on LinkedIn or Slack for details.
242
243
 
@@ -90,13 +90,18 @@ if (programOpts.info) {
90
90
  }
91
91
  }
92
92
 
93
+ let answers = {},
94
+ answer;
95
+
93
96
  if (!programOpts.className) {
94
- questions.push({
97
+ answer = await inquirer.prompt({
95
98
  type : 'input',
96
99
  name : 'className',
97
100
  message: 'Please choose the namespace for your class:',
98
101
  default: 'Covid.view.MyContainer'
99
102
  });
103
+
104
+ Object.assign(answers, answer);
100
105
  }
101
106
 
102
107
  if (!programOpts.baseClass) {
@@ -104,7 +109,7 @@ if (programOpts.info) {
104
109
  type : 'list',
105
110
  name : 'baseClass',
106
111
  message: 'Please pick the base class, which you want to extend:',
107
- default: 'container.Base',
112
+ default: guessBaseClass(programOpts.className || answers.className),
108
113
 
109
114
  choices: [
110
115
  'component.Base',
@@ -128,187 +133,189 @@ if (programOpts.info) {
128
133
  });
129
134
  }
130
135
 
131
- inquirer.prompt(questions).then(answers => {
132
- let baseClass = programOpts.baseClass || answers.baseClass,
133
- className = programOpts.className || answers.className,
134
- singleton = programOpts.singleton || answers.singleton || 'no',
135
- isDrop = programOpts.drop,
136
- isSingleton = singleton === 'yes',
137
- startDate = new Date(),
138
- baseFileName, baseType, classFolder, configName, file, folderDelta, importName, importPath, index, ns, root, rootLowerCase, viewFile;
139
-
140
- if (className.endsWith('.mjs')) {
141
- className = className.slice(0, -4);
142
- }
136
+ answer = await inquirer.prompt(questions);
143
137
 
144
- if (!isDrop) {
145
- ns = className.split('.');
146
- file = ns.pop();
147
- root = ns.shift();
148
- rootLowerCase = root.toLowerCase();
149
- }
138
+ Object.assign(answers, answer);
150
139
 
151
- if (root === 'Neo') {
152
- console.log('todo: create the file inside the src folder');
153
- } else {
154
- if (isDrop === true) {
155
- ns = [];
156
-
157
- let pathInfo = path.parse(cwd),
158
- sep = path.sep,
159
- baseName, loc = baseName = '',
160
- tmpNs;
161
-
162
- sourceRootDirs.some(dir => {
163
- loc = cwd;
164
- tmpNs = [];
165
-
166
- while (pathInfo.root !== loc) {
167
- baseName = path.resolve(loc, './').split(sep).pop();
168
-
169
- if (baseName === dir) {
170
- ns = tmpNs.reverse();
171
- classFolder = path.resolve(loc, ns.join(sep));
172
- file = className;
173
- className = ns.concat(className).join('.');
174
- loc = path.resolve(loc, ns.join(sep));
175
- return true;
176
- }
140
+ let baseClass = programOpts.baseClass || answers.baseClass,
141
+ className = programOpts.className || answers.className,
142
+ singleton = programOpts.singleton || answers.singleton || 'no',
143
+ isDrop = programOpts.drop,
144
+ isSingleton = singleton === 'yes',
145
+ startDate = new Date(),
146
+ baseFileName, baseType, classFolder, configName, file, folderDelta, importName, importPath, index, ns, root, rootLowerCase, viewFile;
147
+
148
+ if (className.endsWith('.mjs')) {
149
+ className = className.slice(0, -4);
150
+ }
151
+
152
+ if (!isDrop) {
153
+ ns = className.split('.');
154
+ file = ns.pop();
155
+ root = ns.shift();
156
+ rootLowerCase = root.toLowerCase();
157
+ }
177
158
 
178
- tmpNs.push(baseName);
179
- loc = path.resolve(loc, '../');
159
+ if (root === 'Neo') {
160
+ console.log('todo: create the file inside the src folder');
161
+ } else {
162
+ if (isDrop === true) {
163
+ ns = [];
164
+
165
+ let pathInfo = path.parse(cwd),
166
+ sep = path.sep,
167
+ baseName, loc = baseName = '',
168
+ tmpNs;
169
+
170
+ sourceRootDirs.some(dir => {
171
+ loc = cwd;
172
+ tmpNs = [];
173
+
174
+ while (pathInfo.root !== loc) {
175
+ baseName = path.resolve(loc, './').split(sep).pop();
176
+
177
+ if (baseName === dir) {
178
+ ns = tmpNs.reverse();
179
+ classFolder = path.resolve(loc, ns.join(sep));
180
+ file = className;
181
+ className = ns.concat(className).join('.');
182
+ loc = path.resolve(loc, ns.join(sep));
183
+ return true;
180
184
  }
181
- });
182
185
 
183
- if (!ns.length) {
184
- console.error(chalk.red(
185
- 'Could not determine namespace for application. Did you provide the ' +
186
- `correct source parent with -s? (was: ${sourceRootDirs.join(',')}`));
187
- process.exit(1);
186
+ tmpNs.push(baseName);
187
+ loc = path.resolve(loc, '../');
188
188
  }
189
+ });
189
190
 
190
- console.info(
191
- chalk.yellow(`Creating ${chalk.bgGreen(className)} extending ${chalk.bgGreen(baseClass)} in ${loc}${sep}${file}.mjs`)
192
- );
193
-
194
- let delta_l = path.normalize(__dirname),
195
- delta_r = path.normalize(loc);
191
+ if (!ns.length) {
192
+ console.error(chalk.red(
193
+ 'Could not determine namespace for application. Did you provide the ' +
194
+ `correct source parent with -s? (was: ${sourceRootDirs.join(',')}`));
195
+ process.exit(1);
196
+ }
196
197
 
197
- if (delta_r.indexOf(delta_l) !== 0) {
198
- console.error(chalk.red(`Could not determine ${loc} being a child of ${__dirname}`));
199
- process.exit(1);
200
- }
198
+ console.info(
199
+ chalk.yellow(`Creating ${chalk.bgGreen(className)} extending ${chalk.bgGreen(baseClass)} in ${loc}${sep}${file}.mjs`)
200
+ );
201
201
 
202
- let delta = delta_r.replace(delta_l, ''),
203
- parts = delta.split(sep);
202
+ let delta_l = path.normalize(__dirname),
203
+ delta_r = path.normalize(loc);
204
204
 
205
- folderDelta = parts.length;
205
+ if (delta_r.indexOf(delta_l) !== 0) {
206
+ console.error(chalk.red(`Could not determine ${loc} being a child of ${__dirname}`));
207
+ process.exit(1);
206
208
  }
207
209
 
208
- if (isDrop !== true) {
209
- if (fs.existsSync(path.resolve(cwd, 'apps', rootLowerCase))) {
210
- classFolder = path.resolve(cwd, 'apps', rootLowerCase, ns.join('/'));
211
- } else {
212
- console.log('\nNon existing neo app name:', chalk.red(root));
213
- process.exit(1);
214
- }
215
- }
210
+ let delta = delta_r.replace(delta_l, ''),
211
+ parts = delta.split(sep);
216
212
 
217
- if (folderDelta === undefined) {
218
- folderDelta = ns.length + 2;
213
+ folderDelta = parts.length;
214
+ }
215
+
216
+ if (isDrop !== true) {
217
+ if (fs.existsSync(path.resolve(cwd, 'apps', rootLowerCase))) {
218
+ classFolder = path.resolve(cwd, 'apps', rootLowerCase, ns.join('/'));
219
+ } else {
220
+ console.log('\nNon existing neo app name:', chalk.red(root));
221
+ process.exit(1);
219
222
  }
223
+ }
224
+
225
+ if (folderDelta === undefined) {
226
+ folderDelta = ns.length + 2;
227
+ }
220
228
 
221
- fs.mkdirpSync(classFolder);
229
+ fs.mkdirpSync(classFolder);
222
230
 
223
- baseFileName = baseClass.split('.').pop();
231
+ baseFileName = baseClass.split('.').pop();
224
232
 
225
- if (baseFileName === file) {
226
- baseFileName = baseClass.split('.');
227
- baseFileName = baseFileName.map(e => capitalize(e)).join('');
228
- }
233
+ if (baseFileName === file) {
234
+ baseFileName = baseClass.split('.');
235
+ baseFileName = baseFileName.map(e => capitalize(e)).join('');
236
+ }
229
237
 
230
- fs.writeFileSync(path.join(classFolder, file + '.mjs'), createContent({
231
- baseClass,
232
- baseFileName,
233
- className,
234
- isSingleton,
235
- file,
236
- folderDelta,
237
- ns,
238
- root
239
- }));
240
-
241
- switch(baseClass) {
242
- case 'controller.Component': {
243
- baseType = 'Neo.controller.Component';
244
- configName = 'controller';
245
- importName = file;
246
- importPath = `./${importName}.mjs`;
247
- index = file.indexOf('Controller');
248
-
249
- if (index > 0) {
250
- viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
251
-
252
- if (fs.existsSync(viewFile)) {
253
- adjustView({baseType, configName, importName, importPath, viewFile});
254
- }
238
+ fs.writeFileSync(path.join(classFolder, file + '.mjs'), createContent({
239
+ baseClass,
240
+ baseFileName,
241
+ className,
242
+ isSingleton,
243
+ file,
244
+ folderDelta,
245
+ ns,
246
+ root
247
+ }));
248
+
249
+ switch(baseClass) {
250
+ case 'controller.Component': {
251
+ baseType = 'Neo.controller.Component';
252
+ configName = 'controller';
253
+ importName = file;
254
+ importPath = `./${importName}.mjs`;
255
+ index = file.indexOf('Controller');
256
+
257
+ if (index > 0) {
258
+ viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
259
+
260
+ if (fs.existsSync(viewFile)) {
261
+ adjustView({baseType, configName, importName, importPath, viewFile});
255
262
  }
256
- break;
257
263
  }
264
+ break;
265
+ }
258
266
 
259
- case 'data.Store': {
260
- baseType = 'Neo.data.Model';
261
- configName = 'model';
262
- importName = className.replace('.store.', '.model.');
267
+ case 'data.Store': {
268
+ baseType = 'Neo.data.Model';
269
+ configName = 'model';
270
+ importName = className.replace('.store.', '.model.');
263
271
 
264
- if (importName.endsWith('ies')) {
265
- importName.replace(new RegExp('ies$'), 'y')
266
- } else {
267
- importName = importName.slice(0, -1);
268
- }
272
+ if (importName.endsWith('ies')) {
273
+ importName.replace(new RegExp('ies$'), 'y')
274
+ } else {
275
+ importName = importName.slice(0, -1);
276
+ }
269
277
 
270
- viewFile = importName.split('.');
271
- viewFile.shift();
278
+ viewFile = importName.split('.');
279
+ viewFile.shift();
272
280
 
273
- importPath = `../${viewFile.join('/')}.mjs`;
274
- viewFile = path.join(classFolder, importPath);
281
+ importPath = `../${viewFile.join('/')}.mjs`;
282
+ viewFile = path.join(classFolder, importPath);
275
283
 
276
- // checking for the data.Model file
277
- if (fs.existsSync(viewFile)) {
278
- // adjusting the data.Store file
279
- viewFile = path.join(classFolder, file + '.mjs');
280
- importName = importName.split('.');
281
- importName = importName.pop();
284
+ // checking for the data.Model file
285
+ if (fs.existsSync(viewFile)) {
286
+ // adjusting the data.Store file
287
+ viewFile = path.join(classFolder, file + '.mjs');
288
+ importName = importName.split('.');
289
+ importName = importName.pop();
282
290
 
283
- adjustView({baseType, configName, importName, importPath, viewFile});
284
- }
285
- break;
291
+ adjustView({baseType, configName, importName, importPath, viewFile});
286
292
  }
293
+ break;
294
+ }
287
295
 
288
- case 'model.Component': {
289
- baseType = 'Neo.model.Component';
290
- configName = 'model';
291
- importName = file;
292
- importPath = `./${importName}.mjs`;
293
- index = file.indexOf('Model');
296
+ case 'model.Component': {
297
+ baseType = 'Neo.model.Component';
298
+ configName = 'model';
299
+ importName = file;
300
+ importPath = `./${importName}.mjs`;
301
+ index = file.indexOf('Model');
294
302
 
295
- if (index > 0) {
296
- viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
303
+ if (index > 0) {
304
+ viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
297
305
 
298
- if (fs.existsSync(viewFile)) {
299
- adjustView({baseType, configName, importName, importPath, viewFile});
300
- }
306
+ if (fs.existsSync(viewFile)) {
307
+ adjustView({baseType, configName, importName, importPath, viewFile});
301
308
  }
302
- break;
303
309
  }
310
+ break;
304
311
  }
305
312
  }
313
+ }
306
314
 
307
- const processTime = (Math.round((new Date - startDate) * 100) / 100000).toFixed(2);
308
- console.log(`\nTotal time for ${programName}: ${processTime}s`);
315
+ const processTime = (Math.round((new Date - startDate) * 100) / 100000).toFixed(2);
316
+ console.log(`\nTotal time for ${programName}: ${processTime}s`);
309
317
 
310
- process.exit();
311
- });
318
+ process.exit();
312
319
 
313
320
  /**
314
321
  * Adds a comma to the last element of the contentArray
@@ -592,4 +599,24 @@ if (programOpts.info) {
592
599
 
593
600
  return classContent.join(os.EOL);
594
601
  }
602
+
603
+ function guessBaseClass(className) {
604
+ if (className.includes('.model.')) {
605
+ return 'data.Model';
606
+ }
607
+
608
+ if (className.includes('.store.')) {
609
+ return 'data.Store';
610
+ }
611
+
612
+ if (className.endsWith('Controller')) {
613
+ return 'controller.Component';
614
+ }
615
+
616
+ if (className.endsWith('Model')) {
617
+ return 'model.Component';
618
+ }
619
+
620
+ return 'container.Base';
621
+ }
595
622
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "4.0.71",
3
+ "version": "4.0.74",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -45,12 +45,12 @@
45
45
  "autoprefixer": "^10.4.7",
46
46
  "chalk": "^5.0.1",
47
47
  "clean-webpack-plugin": "^4.0.0",
48
- "commander": "^9.3.0",
48
+ "commander": "^9.4.0",
49
49
  "cssnano": "^5.1.12",
50
50
  "envinfo": "^7.8.1",
51
51
  "fs-extra": "^10.1.0",
52
52
  "highlightjs-line-numbers.js": "^2.8.0",
53
- "inquirer": "^9.0.0",
53
+ "inquirer": "^9.0.1",
54
54
  "neo-jsdoc": "^1.0.1",
55
55
  "neo-jsdoc-x": "^1.0.4",
56
56
  "postcss": "^8.4.14",
@@ -78,6 +78,14 @@ const DefaultConfig = {
78
78
  * @type Boolean
79
79
  */
80
80
  isInsideSiesta: false,
81
+ /**
82
+ * delay in ms for the worker.Manager:loadApplication() call
83
+ * @default 20
84
+ * @memberOf! module:Neo
85
+ * @name config.loadApplicationDelay
86
+ * @type Number
87
+ */
88
+ loadApplicationDelay: 20,
81
89
  /**
82
90
  * Used by Intl.DateTimeFormat, for details take a look at:
83
91
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
@@ -397,10 +397,7 @@ class Base extends CoreBase {
397
397
  * Removes all items and clears the map
398
398
  */
399
399
  clear() {
400
- let me = this;
401
-
402
- me._items.splice(0, me.getCount());
403
- me.map.clear();
400
+ this.splice(0, this.getCount());
404
401
  }
405
402
 
406
403
  /**
@@ -411,6 +408,16 @@ class Base extends CoreBase {
411
408
  this.filters = restoreOriginalFilters ? Neo.clone(this.originalConfig.filters, true, true) : null;
412
409
  }
413
410
 
411
+ /**
412
+ * Removes all items and clears the map, without firing a mutate event
413
+ */
414
+ clearSilent() {
415
+ let me = this;
416
+
417
+ me._items.splice(0, me.getCount());
418
+ me.map.clear();
419
+ }
420
+
414
421
  /**
415
422
  * Clears all current sorters and optionally restores the original ones in case they existed.
416
423
  * Without restoreInitialState as true this will not affect the current sorting of this collection.
@@ -460,7 +467,7 @@ class Base extends CoreBase {
460
467
  destroy() {
461
468
  let me = this;
462
469
 
463
- me.items.splice(0, me._items.length);
470
+ me._items.splice(0, me._items.length);
464
471
  me.map.clear();
465
472
 
466
473
  super.destroy();
@@ -658,7 +665,7 @@ class Base extends CoreBase {
658
665
  needsSorting = true;
659
666
  }
660
667
 
661
- me.clear();
668
+ me.clearSilent();
662
669
 
663
670
  me.items = [...me.allItems._items];
664
671
  me.map.set(...me.allItems.map);
@@ -1092,8 +1099,8 @@ class Base extends CoreBase {
1092
1099
  * If the toRemoveArray is used, then the index is not used for removing, the entries are found by key and removed from where they are.
1093
1100
  * If index is not passed, toAddArray is appended to the Collection.
1094
1101
  * @param {Number|null} index
1095
- * @param {Number|Array} [removeCountOrToRemoveArray]
1096
- * @param {Array| Object} [toAddArray]
1102
+ * @param {Number|Object[]} [removeCountOrToRemoveArray]
1103
+ * @param {Object|Object[]} [toAddArray]
1097
1104
  * @returns {Object} An object containing the addedItems & removedItems arrays
1098
1105
  */
1099
1106
  splice(index, removeCountOrToRemoveArray, toAddArray) {
@@ -1108,7 +1115,7 @@ class Base extends CoreBase {
1108
1115
  toRemoveArray = Array.isArray(removeCountOrToRemoveArray) ? removeCountOrToRemoveArray : null,
1109
1116
  i, item, key, len, toAddMap;
1110
1117
 
1111
- if (!index && removeCountAtIndex) {
1118
+ if (!Util.isNumber(index) && removeCountAtIndex) {
1112
1119
  Logger.error(me.id + ': If index is not passed, removeCountAtIndex cannot be used');
1113
1120
  }
1114
1121
 
@@ -33,7 +33,12 @@ class Base extends CoreBase {
33
33
  construct(config) {
34
34
  super.construct(config);
35
35
 
36
- HashHistory.on('change', this.onHashChange, this);
36
+ let me = this,
37
+ currentHash = HashHistory.first();
38
+
39
+ currentHash && me.onHashChange(currentHash, null);
40
+
41
+ HashHistory.on('change', me.onHashChange, me);
37
42
  }
38
43
 
39
44
  /**
@@ -228,9 +228,7 @@ class Container extends BaseContainer {
228
228
  * @protected
229
229
  */
230
230
  beforeSetSelectionModel(value, oldValue) {
231
- if (oldValue) {
232
- oldValue.destroy();
233
- }
231
+ oldValue?.destroy();
234
232
 
235
233
  return ClassSystemUtil.beforeSetInstance(value, RowModel);
236
234
  }
@@ -242,9 +240,7 @@ class Container extends BaseContainer {
242
240
  * @protected
243
241
  */
244
242
  beforeSetStore(value, oldValue) {
245
- if (oldValue) {
246
- oldValue.destroy();
247
- }
243
+ oldValue?.destroy();
248
244
 
249
245
  if (value) {
250
246
  let me = this,
@@ -285,7 +281,7 @@ class Container extends BaseContainer {
285
281
  }
286
282
 
287
283
  /**
288
- * @param columns
284
+ * @param {Object[]} columns
289
285
  * @returns {*}
290
286
  */
291
287
  createColumns(columns) {
@@ -302,9 +298,7 @@ class Container extends BaseContainer {
302
298
  Neo.logError('Attempting to create a docked column without a defined width', column, me.id);
303
299
  }
304
300
 
305
- if (columnDefaults) {
306
- Neo.assignDefaults(column, columnDefaults);
307
- }
301
+ columnDefaults && Neo.assignDefaults(column, columnDefaults);
308
302
 
309
303
  if (sorters?.[0]) {
310
304
  if (column.dataField === sorters[0].property) {
@@ -431,7 +425,7 @@ class Container extends BaseContainer {
431
425
  }
432
426
 
433
427
  /**
434
- * @param {Array} data
428
+ * @param {Object[]} data
435
429
  * @protected
436
430
  */
437
431
  onStoreLoad(data) {
@@ -22,11 +22,6 @@ class HashHistory extends Base {
22
22
  * @protected
23
23
  */
24
24
  className: 'Neo.util.HashHistory',
25
- /**
26
- * @member {String} ntype='hash-history'
27
- * @protected
28
- */
29
- ntype: 'hash-history',
30
25
  /**
31
26
  * @member {Boolean} singleton=true
32
27
  * @protected
@@ -71,10 +66,10 @@ class HashHistory extends Base {
71
66
  stack.unshift(data);
72
67
 
73
68
  if (stack.length > me.maxItems) {
74
- stack.length = me.maxItems;
69
+ stack.pop();
75
70
  }
76
71
 
77
- me.fire('change', data, stack[1]);
72
+ me.fire('change', data, stack[1] || null);
78
73
  }
79
74
  }
80
75
 
@@ -246,10 +246,7 @@ class Base extends CoreBase {
246
246
  let message = me.sendMessage(dest, opts, transfer),
247
247
  msgId = message.id;
248
248
 
249
- me.promises[msgId] = {
250
- resolve,
251
- reject
252
- };
249
+ me.promises[msgId] = { reject, resolve };
253
250
  });
254
251
  }
255
252
 
@@ -252,7 +252,7 @@ class Manager extends Base {
252
252
  if (me.constructedThreads === me.activeWorkers) {
253
253
  NeoConfig.appPath && setTimeout(() => { // better save than sorry => all remotes need to be registered
254
254
  me.loadApplication(NeoConfig.appPath);
255
- }, 20);
255
+ }, NeoConfig.loadApplicationDelay);
256
256
  }
257
257
  }
258
258
 
@@ -52,7 +52,6 @@ class RemoteMethodAccess extends Base {
52
52
  if (remote.destination === Neo.workerId) {
53
53
  let me = this,
54
54
  className = remote.className,
55
- exists = false,
56
55
  methods = remote.methods,
57
56
  pkg = Neo.ns(className, true);
58
57
 
@@ -63,15 +62,8 @@ class RemoteMethodAccess extends Base {
63
62
 
64
63
  if (!pkg[method] ) {
65
64
  pkg[method] = me.generateRemote(remote, method);
66
- } else {
67
- exists = true;
68
65
  }
69
66
  });
70
-
71
- // todo: inspect if this can get removed
72
- if (!exists && Neo.workerId !== 'main') {
73
- me.fire('remoteregistered', remote);
74
- }
75
67
  }
76
68
  }
77
69
 
@@ -251,4 +251,28 @@ StartTest(t => {
251
251
  t.isStrict(collection3.getCount(), 5, 'collection3 count is 5');
252
252
  t.isStrict(collection3.allItems.getCount(), 9, 'collection3 allItems count is 9');
253
253
  });
254
+
255
+ t.it('Add & remove at same time', t => {
256
+ collection = Neo.create(Collection, {
257
+ items: [
258
+ {id: 'a'},
259
+ {id: 'b'},
260
+ {id: 'c'},
261
+ {id: 'd'},
262
+ {id: 'e'},
263
+ {id: 'f'}
264
+ ]
265
+ });
266
+
267
+ collection.splice(2, [{id: 'a'}, {id: 'd'}, {id: 'f'}], [{id: 'x'}, {id: 'y'}, {id: 'z'}]);
268
+
269
+ t.isDeeplyStrict(collection.getRange(), [
270
+ {id: 'b'},
271
+ {id: 'c'},
272
+ {id: 'x'},
273
+ {id: 'y'},
274
+ {id: 'z'},
275
+ {id: 'e'}
276
+ ], 'collection.getRange()');
277
+ });
254
278
  });