spice-js 2.7.17 → 2.7.19

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.
@@ -17,6 +17,7 @@
17
17
  const { createTestModel, seedDatabase, clearDatabase } = require('../helpers/modelFactory');
18
18
  const { expectValidIdsOnly, expectUniqueIds, expectNoEmptyIdCalls } = require('../helpers/assertions');
19
19
  const { emptyIdCases, sampleDbResults, relationshipData, duplicateIdData, generateLargeDataset } = require('../fixtures/testData');
20
+ const SpiceModel = require('../../src/models/SpiceModel').default;
20
21
 
21
22
  describe('SpiceModel - Critical Fixes for Empty/Null Values', () => {
22
23
 
@@ -167,6 +168,104 @@ describe('SpiceModel - Critical Fixes for Empty/Null Values', () => {
167
168
  expect(result.name).toBe('Alice');
168
169
  expect(result.email).toBe('alice@example.com');
169
170
  });
171
+
172
+ test('should keep deep dot-notation under the first nested segment', () => {
173
+ const rows = [{
174
+ id: 'user-1',
175
+ first_name: '',
176
+ committee_votes: [
177
+ {
178
+ member: {
179
+ first_name: 'Ada',
180
+ last_name: 'Lovelace'
181
+ },
182
+ value: 'yes'
183
+ }
184
+ ]
185
+ }];
186
+
187
+ const [result] = model.filterResultsByColumns(
188
+ rows,
189
+ '`committee_votes`.`member`.`first_name`'
190
+ );
191
+
192
+ expect(result.first_name).toBeUndefined();
193
+ expect(result.committee_votes).toBeDefined();
194
+ expect(result.committee_votes[0].member.first_name).toBe('Ada');
195
+ });
196
+
197
+ test('should remove base model prefix before selecting nested segment', () => {
198
+ const rows = [{
199
+ id: 'user-1',
200
+ group: {
201
+ name: 'Admins'
202
+ }
203
+ }];
204
+
205
+ const [result] = model.filterResultsByColumns(
206
+ rows,
207
+ '`user`.`group`.`name`'
208
+ );
209
+
210
+ expect(result.group).toBeDefined();
211
+ expect(result.group.name).toBe('Admins');
212
+ });
213
+
214
+ test('should keep mixed scalar and mapped columns without empty select slots', () => {
215
+ const applicationModel = createTestModel({ type: 'application' });
216
+ const requestedColumns = 'meta().id,dependents,dd_assessment_notes,ceo_assessment_notes,first_name,title,last_name,project,`committee_votes`.`member`,`committee_votes`.`date_of_vote`,`committee_votes`.`vote`,`committee_votes`.`rejection_reasons`,`committee_votes`.`vote_justification`,`committee_votes`.`comments`,`committee_votes`.`application`,control_number,full_name,application_type,stage,committee_submission_date,votes_casted';
217
+
218
+ const corrected = applicationModel.correctColumns(
219
+ requestedColumns,
220
+ [{ alias: 'committee_votes' }]
221
+ );
222
+
223
+ const selectedColumns = corrected
224
+ .split(',')
225
+ .map((item) => item.trim());
226
+
227
+ expect(selectedColumns.every((item) => item !== '')).toBe(true);
228
+ expect(selectedColumns).toContain('dependents');
229
+ expect(selectedColumns).toContain('first_name');
230
+ expect(selectedColumns).toContain('`application`.`committee_votes`');
231
+ expect(selectedColumns).toContain('meta().id');
232
+ });
233
+
234
+ test('should collapse deep mapped paths even without mapped nestings', () => {
235
+ expect(
236
+ SpiceModel.prototype.correctColumns.call(
237
+ {
238
+ type: 'application',
239
+ props: {
240
+ dependents: {
241
+ map: {
242
+ reference: 'dependent'
243
+ }
244
+ }
245
+ }
246
+ },
247
+ 'dependents.passports.holder',
248
+ [],
249
+ )
250
+ ).toBe('`application`.`dependents`');
251
+
252
+ expect(
253
+ SpiceModel.prototype.correctColumns.call(
254
+ {
255
+ type: 'application',
256
+ props: {
257
+ dependents: {
258
+ map: {
259
+ reference: 'dependent'
260
+ }
261
+ }
262
+ }
263
+ },
264
+ '`application`.`dependents`.`passports`.`holder`',
265
+ [],
266
+ )
267
+ ).toBe('`application`.`dependents`');
268
+ });
170
269
  });
171
270
 
172
271
  describe('Fix #4-6: mapToObject() - Strict Checks and Deduplication', () => {
@@ -258,6 +357,63 @@ describe('SpiceModel - Critical Fixes for Empty/Null Values', () => {
258
357
  });
259
358
  });
260
359
 
360
+ describe('Fix #8: mapToObjectArray() - Empty Key Projection Fallback', () => {
361
+ let model;
362
+
363
+ beforeEach(() => {
364
+ model = createTestModel({ type: 'user' });
365
+ });
366
+
367
+ afterEach(() => {
368
+ clearDatabase(model);
369
+ });
370
+
371
+ test('should map array relationships when source field is under empty-string key', async () => {
372
+ let capturedIds = [];
373
+ let capturedColumns = [];
374
+
375
+ class FakeRelatedModel {
376
+ async getMulti({ ids, columns }) {
377
+ capturedIds = ids;
378
+ capturedColumns = columns;
379
+ return [
380
+ { id: 'group-1', type: 'group', name: 'Admins' },
381
+ { id: 'group-2', type: 'group', name: 'Editors' }
382
+ ];
383
+ }
384
+ }
385
+
386
+ const sourceRows = [
387
+ {
388
+ id: 'user-1',
389
+ type: 'user',
390
+ '': {
391
+ groups: ['group-1', 'group-2']
392
+ }
393
+ }
394
+ ];
395
+
396
+ const mapped = await model.mapToObjectArray(
397
+ sourceRows,
398
+ FakeRelatedModel,
399
+ 'groups',
400
+ 'groups',
401
+ {},
402
+ {},
403
+ 'read',
404
+ 3,
405
+ 0,
406
+ 'groups.name'
407
+ );
408
+
409
+ expect(capturedIds).toEqual(['group-1', 'group-2']);
410
+ expect(capturedColumns).toEqual(['name']);
411
+ expect(mapped[0].groups).toHaveLength(2);
412
+ expect(mapped[0].groups[0].name).toBe('Admins');
413
+ expect(mapped[0].groups[1].name).toBe('Editors');
414
+ });
415
+ });
416
+
261
417
  describe('Fix #7: Strict Equality in ID Matching', () => {
262
418
  let model;
263
419